初めに
JS初心者です。普段はPHPやJavaを書いています。
シングルスレッドであるJSがどのように非同期処理を行っているのかが気になったので調べてみました。
JavaScript実行環境
まずは前提知識から理解しましょう。
JavaScriptはランタイムと呼ばれる実行環境で動作します。ランタイムは、JavaScriptコードを実行するだけでなく、非同期処理やI/O操作などをサポートする機能を提供します。ブラウザやNode.jsなどがそれです。ランタイムの中には実行エンジンと呼ばれるJavaScriptを実行するためのコンポーネントが存在します。
実行エンジン
- JavaScriptを評価、解析、実行する役割を担う
- シングルスレッド
- 例)V8やSpiderMonkeyなど
実行エンジンはシングルスレッド
実行エンジンはシングルスレッドで動作するため、非同期処理そのものを直接扱うことはできません。時間のかかるような処理を実行した場合、後続の処理は待たなければなりません。
そのようなことがないよう、ランタイムの機能によって非同期処理が可能になっています。
非同期処理を実現するためのコンポーネント
ランタイムが提供している非同期処理のためのコンポーネントを紹介します。
Web APIs
- ランタイムが用意しているAPI(
setTimeout
、fetch
など) - 実行エンジンとは別に非同期に処理を実行する際に用いる
- 多くのWeb APIでは、処理完了後に実行する動作をコールバック関数で指定することができる
- 例)1秒後、にコンソールに「終了」と表示したい場合、
setTimeout
にコールバック関数を渡す※setTimeout(() => { console.log("終了"); }, 1000);
fetch
のようにPromiseを返すAPIではコールバック関数を直接渡さずに.thenで後続処理を記述します。
- 例)1秒後、にコンソールに「終了」と表示したい場合、
タスクキュー(コールバックキュー)
- ランタイムで行う処理を管理するためのキュー
- Web APIsで渡されたコールバック関数の処理がキューに積まれる
- キューに積まれるタイミングは「WebAPIs自体の処理が終了した」タイミング
- 例)setTimeoutの場合、〇秒経った後にキューに積まれる
イベントループ
- 実行エンジンとランタイムの橋渡しを行うコンポーネント
- タスクキューに積まれた処理を実行エンジンに依頼する
- 実行エンジン側で実行中の処理がないことを確認して実行を依頼する
非同期処理の例
console.log('1: 開始');
setTimeout(() => {
console.log('2: setTimeoutのタスク');
}, 0);
console.log('3: 終了');
// 実行結果
// 1: 開始
// 3: 終了
// 2: setTimeoutのタスク
-
console.log('1: 開始');
が実行エンジンで実行される -
setTimeout
が呼び出され、タスクキューにconsole.log('2: setTimeoutのタスク');
の処理が積まれる(※ここでは待機時間を0秒にしているため即座に積まれるが、実際は待機時間が完了したタイミングで詰まれる) -
console.log('3: 終了');
が実行エンジンで実行される - 実行エンジンが暇になったことを確認し、イベントループがタスクキューの
console.log('2: setTimeoutのタスク');
の処理を実行エンジンに依頼する - 実行エンジンが
console.log('2: setTimeoutのタスク');
の処理の処理を実行する
終わりに
本当はasync/awaitについて書くつもりだったのですが、書いている途中で理解が浅すぎることに気が付きました。
まだまだ理解できていないので、当分時間はかかりそうです。
参照
https://zenn.dev/msy/scraps/1490bd249046ce
https://zenn.dev/estra/articles/js-async-programming-roadmap