概要: このチュートリアルでは、JavaScriptのイベントループと、イベントループに基づいてJavaScriptがどのように並行モデルを実現するかについて学習します。
JavaScriptシングルスレッドモデル
JavaScriptはシングルスレッドのプログラミング言語です。これは、JavaScriptが一度に1つのことしかできないことを意味します。
JavaScriptエンジンは、ファイルの先頭からスクリプトを実行し、下に向かって処理を進めます。実行フェーズでは、実行コンテキストを作成し、関数をコールスタックにプッシュおよびポップします。
関数の実行に時間がかかる場合、関数の実行中はページがハングするため、Webブラウザを操作できません。
完了に時間がかかる関数は、ブロッキング関数と呼ばれます。技術的には、ブロッキング関数は、マウスクリックなど、Webページ上のすべてのインタラクションをブロックします。
ブロッキング関数の例としては、リモートサーバーからAPIを呼び出す関数があります。
次の例では、大きなループを使用してブロッキング関数をシミュレートしています。
function task(message) {
// emulate time consuming task
let n = 10000000000;
while (n > 0){
n--;
}
console.log(message);
}
console.log('Start script...');
task('Call an API');
console.log('Done!');
Code language: JavaScript (javascript)
この例では、時間のかかるタスクをエミュレートするtask()
関数内に大きなwhile
ループがあります。 task()
関数はブロッキング関数です。
スクリプトは数秒間ハングし(コンピュータの速度によって異なります)、次の出力を発行します。
Start script...
Download a file.
Done!
スクリプトを実行するために、JavaScriptエンジンは最初の呼び出しであるconsole.log()
をコールスタックの最上位に配置して実行します。次に、task()
関数をコールスタックの最上位に配置して、関数を実行します。
ただし、task()
関数が完了するまでには時間がかかります。そのため、メッセージ'Download a file.'
が表示されるのは少し後になります。 task()
関数が完了すると、JavaScriptエンジンはそれをコールスタックからポップします。
最後に、JavaScriptエンジンはconsole.log('Done!')
関数への最後の呼び出しを配置して実行します。これは非常に高速です。

コールバックによる解決
ブロッキング関数が他のアクティビティをブロックしないようにするには、通常、後で実行するためにコールバック関数に配置します。例えば
console.log('Start script...');
setTimeout(() => {
task('Download a file.');
}, 1000);
console.log('Done!');
Code language: JavaScript (javascript)
この例では、メッセージ'Start script...'
と'Done!'
がすぐに表示されます。その後、メッセージ'Download a file'
が表示されます。
出力は次のとおりです。
Start script...
Done!
Download a file.
前述のように、JavaScriptエンジンは一度に1つのことしかできません。ただし、JavaScriptランタイムが一度に1つのことしかできないと言う方がより正確です。
Webブラウザには、JavaScriptエンジンだけでなく、他のコンポーネントもあります。
setTimeout()
関数を呼び出したり、fetchリクエストを作成したり、ボタンをクリックしたりすると、Webブラウザはこれらのアクティビティを同時かつ非同期に実行できます。
setTimeout()
、fetchリクエスト、およびDOMイベントは、WebブラウザのWeb APIの一部です。
この例では、setTimeout()
関数を呼び出すと、JavaScriptエンジンはそれをコールスタックに配置し、Web APIは1秒後に期限切れになるタイマーを作成します。

次に、JavaScriptエンジンは`task()``関数をコールバックキューまたはタスクキューと呼ばれるキューに配置します。

イベントループは、コールバックキューとコールスタックの両方を監視する、常に実行されているプロセスです。
コールスタックが空でない場合、イベントループは空になるまで待機し、コールバックキューから次の関数をコールスタックに配置します。コールバックキューが空の場合、何も起こりません。

別の例を見てみましょう。
console.log('Hi!');
setTimeout(() => {
console.log('Execute immediately.');
}, 0);
console.log('Bye!');
Code language: JavaScript (javascript)
この例では、タイムアウトは0秒なので、メッセージ'Execute immediately.'
はメッセージ`'Bye!'`の前に表示されるはずです。しかし、実際にはそうはなりません。
JavaScriptエンジンは、次の関数呼び出しをコールバックキューに配置し、コールスタックが空になったときに実行します。言い換えれば、JavaScriptエンジンは`console.log('Bye!')``の後に実行します。
console.log('Execute immediately.');
Code language: JavaScript (javascript)
出力は次のとおりです。
Hi!
Bye!
Execute immediately.
次の図は、JavaScriptランタイム、Web API、コールスタック、およびイベントループを示しています。

このチュートリアルでは、並行性を実現するためにコールスタックとコールバックキュー間のタスクを調整する、常に実行されているプロセスであるJavaScriptイベントループについて学習しました。