昔、JavaScript の非同期処理の裏側ではどういった処理が行われているのか疑問に思いました。
当時、新しくスレッドを作って実行しているのかと思ったのですが、そもそも JavaScript はシングルスレッドなので、新しくスレッドを作る事は出来ないので、どうやって非同期処理を実行しているんだろうと思い、 調べていくうちにイベントループが非同期な処理を実現していることを知り、その時はざっくり理解した気になって満足していました。
ですが、最近になってイベントループの TaskQueue は 2 種類存在しているという事を知り、イベントループへの理解が甘いと思い、また改めて学習し直そう思ったのが、モチベーションになります。
シングルスレッドで並行処理を実現できます。
イベントループは JavaScript ランタイムがプログラムを実行するときの基盤となる仕組みです。
イベントループの説明に入る前に前準備として、
まず、JavaScript の大きな特徴として「シングルスレッド」と言う点があります。つまり一度に実行できるタスクは1つになります。例えば、30 秒以上かかるタスクを実行した場合、そのタスクを実行している間は、他のタスクは実行を待つことになります。
JavaScript はデフォルトでブラウザのメインスレッドと呼ばれる描画を処理するスレッドが使用されます。その為、時間のかかるタスクがある場合、UI 全体が動かなくなってしまい、大きく UX を損なってしまいます。
イベントループは主に 3 つの要素を備えています。
CallStack、TaskQueue、Web API になります。
下記で、それぞれ簡単に説明していきます。
関数は呼び出されると CallStack に追加されます。
CallStack は LIFO(後入れ先出し)で機能します。
JavaScript エンジンの内部に実装されています。
コールバック関数は TaskQueue で待機します。
TaskQueue は FIFO(先入れ先出し)の配列で、イベントループは、CallStack が空になる度に、TaskQueue からコールバック関数を取り出して実行します。
JavaScript エンジンの外部に実装されています。
こちらは、TaskQueue と MicroTaskQueue の 2 種類あります。
Web API には DOM Event, setTimeout, Ajax などが含まれます。これらの機能は非同期(ノンブロッキング)に実行されます。
ブラウザが提供する機能です。
上記の3つの要素が下記図の様に構成されています。
引用: JavaScript の非同期処理 Promise、Async と Await の仕組みを GIF アニメで解説
先ほどの図を踏まえて、コードを動かしてみて、実際のイベントループの動きを解説していきたいと思います。
わかりやすいように、コンビニにて、客が弁当を購入する時の店員の対応を下記コードに表しました。
const task1 = () =>
console.log("弁当のバーコードを読み取り、電子レンジに入れる");
const task2 = () => setTimeout(() => console.log("客に弁当を渡す"), 1000);
const task3 = () => console.log("お会計をする");
task1();
task2();
task3();
上記コードで行われていることは、下記のようになります。
以上がイベントループの動きです。
ただ、上記コードは、実際の実務などでのコードとはだいぶ乖離していてイメージしにくいと思いますので、実際に存在しない架空のコードを下記に書いてみました。
const amount = BarcodeReader.read(bento); // 弁当のバーコードを読み取る
microwaveOven.chin(bento, (res) => getHotBento(res)); // 弁当を温める
cashier.payment(amount); // お会計をする
microwaveOven.chin()が非同期になっています。
引数で渡されるコールバック関数の getHotBento()は、弁当を温めた後、TaskQueue にエンキューされます。
弁当を温めている間に cashier.payment()が実行されお会計します。
そして、最後に getHotBento()がデキューされ、CallStack で実行されます。
Web API はブラウザが提供する機能なので、Node.js に Web API は存在しません。
それでは、どうやってイベントループを実現しているかと言うと、システムカーネルに操作をオフロードすることで実現しています。
そして、Node.js は V8 を使用しているのですが、イベントループを提供しているのは、libuv と言う C ライブラリになります。
ですので、JavaScript のイベントループとは異なり、Node.js の Queue には 6 つのフェーズがあります。
興味のある方は、調べてみてください。