イベントループ | JavaScript
イベントループ
イベントループの概要
JavaScript の イベントループ( event loop) は以下のように動作します。
- コールスタックの処理
- コードが順次実行され、関数呼び出しなどがコールスタックに積まれます。
- コールスタックが空になるまで、他のタスク(マイクロタスクやタイマー)は実行されません。
- マイクロタスクの処理
- コールスタックが空になった時点で、マイクロタスクキューに溜まったタスク(例:Promise.then)を順次実行します。
- タスクキューの処理
- マイクロタスクキューが空になった後に、通常のタスクキュー(例:setTimeout のコールバック)が処理されます。
非同期処理の種類
JavaScript の非同期処理は、イベントループを基盤に以下の2種類に分類されます:
| 分類 | 代表的な例 | 実行順序の優先度 |
|---|---|---|
| マイクロタスク | Promise.then, MutationObserver | コールスタックが空の直後に実行 |
| マクロタスク | setTimeout, setInterval, I/O | マイクロタスクがすべて完了した後に実行 |
イベントループの仕組み
- コールスタック(同期処理)が空になるまで実行。
- マイクロタスクキューのタスクを順次実行。
- タスクキュー(マクロタスク)を順次実行。
- 1~3を繰り返す。
マイクロタスク
マイクロタスクは「現在のコールスタックの処理が完了した直後」に実行されます。
優先度が高く、以下が該当します:
- Promise.then, catch, finally: Promise の非同期コールバック。
- MutationObserver: DOM の変更を監視するコールバック。
マクロタスク
マクロタスクは「マイクロタスクがすべて処理された後」に実行されます。
以下が該当します:
- setTimeout / setInterval: タイマーのコールバック。
- I/O 処理: ファイルの読み書きやネットワーク通信。
setTimeout は非同期処理といえるか?
setTimeout のコールバックは非同期処理といえますが、マクロタスクに分類されます。 指定された遅延時間が経過しても、マイクロタスクが優先されるため、コールスタックやマイクロタスクが処理されるまで実行されません。
実行順序の例
console.log('Start');
setTimeout(() => {
console.log('setTimeout'); // マクロタスク
}, 0);
Promise.resolve().then(() => {
console.log('Promise'); // マイクロタスク
});
const observer = new MutationObserver(() => {
console.log('MutationObserver'); // マイクロタスク
});
const target = document.createElement('div');
observer.observe(target, { attributes: true });
target.setAttribute('data-test', 'value');
console.log('End');
// Start
// End
// Promise
// MutationObserver
// setTimeout
注意点
重い同期処理(例: 長いループ)は、非同期処理を遅延させます。
コールスタックがすべて処理されるまで、マイクロタスクもマクロタスクも実行されません。 最小遅延時間:setTimeout(fn, 0) の実行は「即時実行」ではなく、タイマーの最小遅延時間(通常4ms)に依存します。
マイクロタスクの過剰な使用:大量のマイクロタスクが登録されると、マクロタスクが遅延し、UI 更新や I/O 処理が滞る可能性があります。
おすすめの設計方法
- 重い処理の分割:非同期処理(setTimeout や requestAnimationFrame)で小分けに実行。
- UI のフリーズ回避:必要に応じて Web Worker を活用して別スレッドで処理。
- マイクロタスクの適切な使用:Promise を多用する場合、処理が意図した順序で実行されるよう設計。