JavaScriptはシングルスレッドなのに非同期処理ができる理由
JavaScriptはシングルスレッドの言語です。シングルスレッドとは、一度に1つのタスクしか処理できないことを指します。しかし、実際は効率的に複数のタスクを管理ができます。そのように実行できるのはなぜでしょうか?
この仕組みを理解するためには、コールスタック(Call Stack)、イベントループ(Event Loop)、タスクキュー(Task Queue)、マイクロタスク(Microtask queue)、Web API について知る必要があります。今回は、JavaScriptがシングルスレッドなのに非同期処理ができる理由を解説していきます。
この記事の目次
1. JavaScriptランタイム仕組み
イベントループの詳細に入る前に、まずはJavaScriptランタイムの主要な構成要素を理解しましょう。
1.1 コールスタック(Call Stack)
コールスタックは、関数の実行を追跡するスタック構造のデータ構造です。関数が呼び出されるとスタックにプッシュされ、処理が終了するとポップされます。
1.2 イベントループ(Event Loop)
イベントループは、コールスタックとタスクキューを監視し、次にどのタスクを実行すべきかを決定する仕組みです。
1.3 タスクキュー(Task Queue / Macrotask queue)
このキューには、一定のルールに従って1つずつ実行されるタスクが格納されます。代表的なものは以下の通りです。
- setTimeout: 指定した時間が経過した後に関数を実行する。
- setInterval: 一定の間隔で関数を繰り返し実行する。
- setImmediate(Node.jsのみ): 現在のイベントループサイクルが終了した後に関数を実行する。
- I/O操作: ファイルの読み書きやネットワーク通信など。
- UI更新: レイアウト変更(Reflow)や画面の再描画(Repaint)など。
1.4 マイクロタスクキュー(Microtask Queue)
マイクロタスクは、通常のタスク(マクロタスク)よりも優先的に実行される短い処理 です。以下のようなものが含まれます。
Promise(then/catch/finally): Promiseの解決(resolve)や失敗(reject)に応じてコールバックを実行する。
MutationObserver: DOMの変更を監視し、変更が発生した際にコールバックを実行する。
queueMicrotask: 現在のタスクが完了した直後に実行されるマイクロタスクをスケジュールする。
1.5 Web API
Web APIは、ブラウザやNode.jsで提供されるAPIであり、JavaScriptが非同期タスクを実行する際に利用されます。
例えば、以下のようなものがあります。
- Fetch API: ネットワークリクエストを非同期に実行するためのAPI
- DOM API: ドキュメントオブジェクトモデルを操作するためのAPI
- Geolocation API: ユーザーの位置情報を取得するためのAPI
- Web Storage API: クライアント側にデータを保存するためのAPI
2. 非同期処理の仕組みとイベントループの流れ
2.1 同期処理と非同期処理の違い
同期処理(Synchronous Execution)
コードは 上から順に 実行され、すべての処理がコールスタックに積まれて処理される。処理が完了するまで次の処理は開始されない。
非同期処理(Asynchronous Execution)
setTimeout や fetch などの非同期APIは、処理を Web APIやNode.js APIにオフロードする。APIの処理が完了すると、対応するコールバック関数が タスクキュー(マクロタスクキュー)またはマイクロタスクキュー に追加され、イベントループによって適切なタイミングで実行される。
2.2 イベントループの流れ
イベントループ(Event Loop)は、JavaScriptの並行処理を支える重要な仕組みです。シングルスレッドでありながら、非同期処理を効率的に管理する役割を持っています。
2.2.1 コールスタックが空であることを確認
JavaScriptの実行は基本的にコールスタックで処理されるため、現在実行中のタスクが完了し、コールスタックが空になるのを待つ。
2.2.2 マイクロタスクキュー内のタスクをすべて実行
Promise.then() や MutationObserver などのマイクロタスクを、キューが空になるまで順番に処理する。
2.2.3 タスクキュー(マクロタスクキュー)の先頭のタスクを実行
setTimeout や setInterval などのマクロタスクをキューから取り出し、1つずつ処理する。
2.2.4 ステップ1に戻り、繰り返す
イベントループはこの流れを継続し、タスクがなくなるまで実行し続ける。
例 Geolocation APIでユーザーの位置情報を取得する
以下のコードは、非同期処理の流れを示す例です。
function showPosition(position) { console.log("Latitude: " + position.coords.latitude + ", Longitude: " + position.coords.longitude); } function showError(error) { console.error("Error occurred: ", error); } navigator.geolocation.getCurrentPosition(showPosition, showError);
実行の流れ
1. 非同期処理の開始
navigator.geolocation.getCurrentPosition(showPosition, showError) を実行すると、ブラウザの Web API に対して非同期リクエストが送信される。
2. Web API による処理
ブラウザの Web API がバックグラウンドで位置情報の取得を試みる。
3. タスクキューへの登録
位置情報の取得が完了すると、成功時のコールバック showPosition または失敗時のコールバック showError が マクロタスクキュー に登録される。
4. イベントループによる処理
イベントループは コールスタックが空であることを確認 し、タスクキューから showPosition または showError を取得して実行する。
3. まとめ
JavaScriptはシングルスレッドだが、非同期処理を管理する仕組み(イベントループ、タスクキュー、マイクロタスク)によって効率的に並行処理を行うことができます。タスクキュー(マクロタスク)とマイクロタスクの違いを理解すると、より正確なコードの動作を予測できるでしょう。ブラウザAPIやNode.js APIと連携することで、非同期処理をうまく活用することが重要になりますので、是非参考になさってください。
この情報は役に立ちましたか?
カテゴリー: