はじめに
JavaScriptの非同期処理について、詳しく調べる機会があったので備忘録をまとめたいと思います。
用語解説
本題に入るにあたり、出てくる用語についてまとめます。
コールスタック(call stack)
コールスタック(call stack)とは、コンピュータプログラムの実行時に用いられるメモリ領域の一つで、関数などを呼び出す際に呼び出し元の情報や引き渡す引数の値などを一時的に保管しておくためのもの。
タスクキュー
非同期処理のコールバック関数を一時的に保存しておくためのキュー。
コールスタックが空になると、タスクキューから処理を取り出して実行する。
[by ChatGPT]
イベントループ
イベントループ(Event Loop)とは、JavaScriptの非同期処理を効率的に実行するための仕組みです。
JavaScriptはシングルスレッド(一度に1つの処理しか実行できない)ですが、このイベントループの仕組みを使うことで、非同期処理(例: タイマーやAPIリクエストなど)を実現しています。
[by ChatGPT]
JavaScriptエンジンの主要なコンポーネント
[by ChatGPT]
図解:ブラウザの全体像
1. 同期処理
まず初めに、JavaScriptの同期処理について説明します。
JavaScriptは、シングルスレッドで動作するため、ソースの上から下へ順に処理が実行されます。
例えば以下の実装の場合、printLogメソッドの中で標準出力していますが、結果はconsole.logの呼び出し順に出力されます。
function printLog() {
console.log("1: 同期処理開始");
console.log("2: 同期処理終了");
}
// 関数を実行
printLog();
1: 同期処理開始
2: 同期処理終了
単純にみえるこの処理ですが、実は裏ではJavaScriptエンジンが以下の流れで処理行っています。
-
JavaScriptエンジンがコードをパース・解釈し、インタープリンターが
printLog()
をコールスタックに積んで実行
→コールスタックの状態:[printLog] -
console.log("1:同期処理開始")
がprintLog()
内で呼ばれる = インタープリンターがコールスタックに積んで実行
→コールスタックの状態:[printLog,console.log] -
2.でコールスタックに積んだ
console.log("1:同期処理開始")
が終了後、コールスタックから削除
→コールスタックの状態:[printLog] -
console.log("2:同期処理終了")
がprintLog()
内で呼ばれる = インタープリンターがコールスタックに積んで実行
→コールスタックの状態:[printLog,console.log] -
4.でコールスタックに積んだ
console.log("2:同期処理終了")
が終了後、コールスタックから削除
→コールスタックの状態:[printLog] -
1.でコールスタックに積んだ
printLog()
が終了後、コールスタックから削除
→コールスタックの状態:[]
このように、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とイベントループ
JavaScript イベントループの仕組みをGIFアニメで分かりやすく解説
JavaScriptのイベントループまわりは、どういう仕組みで動いているのか?
JavaScriptにおける非同期処理とは