はじめに
JavaScriptの非同期処理について、詳しく調べる機会があったので備忘録をまとめたいと思います。
1. 同期処理
まず初めに、JavaScriptの同期処理について説明します。
JavaScriptは、基本的にシングルスレッドで動作するため、ソースの上から下へ順に処理が実行されます。
例えば以下の実装の場合、printLogメソッドの中で標準出力していますが、結果はconsole.logの呼び出し順に出力されます。
function printLog() {
console.log("1: 同期処理開始");
console.log("2: 同期処理終了");
}
// 関数を実行
printLog();
1: 同期処理開始
2: 同期処理終了
単純にみえるこの処理ですが、実は裏ではJavaScriptエンジンが以下の流れで処理行っています。
- JavaScriptエンジンがコードをパース・解釈し、インタープリンターが
printLog()
をコールスタックに積んで実行
2. console.log("1:同期処理開始")
がprintLog()
内で呼ばれる。
→インタープリンターがコールスタックに積んで実行
3. 2.でコールスタックに積んだconsole.log("1:同期処理開始")
が終了後、コールスタックから削除
4. console.log("2:同期処理終了")
がprintLog()
内で呼ばれる。
→ インタープリンターがコールスタックに積んで実行
5. 4.でコールスタックに積んだconsole.log("2:同期処理終了")
が終了後、コールスタックから削除
6. 1.でコールスタックに積んだprintLog()
が終了後、コールスタックから削除
このように、JavaScriptではソースが上から順に同期的に処理されます。
(補足)JavaScriptエンジンの主要なコンポーネント
2. 非同期処理
さて、ここからが本題です。
前項で記載した通り、JavaScriptは基本的にシングルスレッドであるため、処理が並列して実行されることはありません。
例えば、ネットワーク通信やファイル読み込みのような時間がかかる処理であっても、処理を同期的に行えば(非同期処理を使用しなければ)、前段の処理が完了するまで他の処理は止まってしまいます。
以下に例を示します。
例:非同期処理を使わないとどうなるか?
console.log("Start");
function waitSync() {
let start = Date.now();
while (Date.now() - start < 3000) {
// 3秒間何もしない(ブロッキング)
}
console.log("Sync Done");
}
waitSync();
console.log("End");
###### 結果 ######
Start
(3秒間フリーズ)
Sync Done
End
このようにJavaScriptはシングルスレッドであるため、実行時間の長い処理を呼び出すと、その処理の完了まで他の処理を動かすことができません。
この問題を解決してくれるのが非同期処理です。
非同期処理とは
長く続く可能性のあるタスクを開始しても、そのタスクが完了するまで待つのではなく、そのタスクの実行中も他のイベントに応答できるようにする技術です。
(引用元)非同期 JavaScript 入門
例:非同期処理を使うとどうなる?
console.log("Start");
setTimeout(() => {
console.log("Async Done");
}, 3000);
console.log("End");
###### 結果 ######
Start
End
(3秒後に)Async Done
上記のように非同期処理(ここではsetTimeout()
)を使うと、3秒間待つ間に console.log("End")
が先に実行され、その後、コールバックに指定したconsole.log("Async Done")
が実行されます。
→時間のかかる処理の終了を待たずに、他の処理を行うことができる!
3. 非同期処理の仕組み
非同期処理については前項で解説した通りです。
前項までを調べたうえで、小生はふと疑問に思いました。
JavaScriptはシングルスレッドなのに、非同期処理使ってる時って並列で処理が動いてない?
ようは前項の例でいくと、setTimeout()
が流れている裏で、console.log("End");
が動いてるやん!
話違うやん!となったわけです。
この疑問についても解消したので、メモしておこうかと思います。
図解:ブラウザの全体像
唐突に図を張りましたが、ブラウザのコンポーネントにはJavascriptエンジンのほかに、以下のものがあります。
Web APIsとは
Web APIとは、Webブラウザが提供する機能をJavaScriptから利用できるようにするインターフェースのことです。これにより、JavaScriptはブラウザ内でさまざまな操作を実行できます。
タスクキューとは
タスクキュー(Task Queue) は、JavaScriptの非同期処理(setTimeout や fetch のコールバックなど)が完了したときに、実行待ちの処理を一時的に保持するキュー(待ち行列) です。
イベントループ
Web APIとは、Webブラウザが提供する機能をJavaScriptから利用できるようにするインターフェースのことです。これにより、JavaScriptはブラウザ内でさまざまな操作を実行できます。
これらのブラウザのコンポーネントがうまいこと処理してくれているわけですが、どのように処理を行っているか例を使って詳細に見ていきます。
console.log("Start");
setTimeout(() => {
console.log("Async Done");
}, 3000);
console.log("End");
- JavaScriptエンジンがコードをパース・解釈し、インタープリンターが
console.log("Start")
をコールスタックに積んで実行
※実行後コールスタックから削除
2. setTimeout()
がコールスタックに積まれ、Web API にタイマー開始を要請
※要請が済めばコールスタックからは削除
3-1. 2.で実行したsetTimeout()
が終了後※3秒経過後、コールバックに設定した内容console.log("Async Done");
をタスクキューに設定
3-2. 2.でWeb APIにsetTimeout()
を任せている間、コールスタックが空くので、
次の処理console.log("End");
を実行
※実行後コールスタックから削除
4. console.log("End");
の実行後、コールスタックが空くかつ、プログラムのトップダウンに他の関数も残っていないので、タスクキューにためておいたconsole.log("Async Done");
を実行する。
→タスクキューに空きがあるかを常にイベントループが監視していて、空きができ次第、タスクキューにためておいた処理を実行する!
まとめ
今回は、JvaScriptの非同期処理について、まとめてみました。
調べてみると中々奥が深いですな。
参考にした、参考になるサイト
JavaScriptとイベントループ
JavaScript イベントループの仕組みをGIFアニメで分かりやすく解説
JavaScriptのイベントループまわりは、どういう仕組みで動いているのか?
JavaScriptにおける非同期処理とは