概要
非同期処理のバグや意図しない順序の実行は、仕様を知らずに「動いているからOK」としてしまったコードから生まれる。
その根底にあるのが、Event Loop という実行モデルだ。
Event Loopを理解することは、JavaScriptがいつ、何を、どの順番で実行するのかを制御することであり、
Promise・setTimeout・DOMイベント・fetch など、すべての非同期動作の共通語となる。
本記事では、Event Loopの全体構造を視覚的に・構造的に整理し、現場で即使える判断軸を提供する。
対象環境
ブラウザ・Node.js問わずJavaScript全般(ES6以降)
Event Loopの全体像(概念図)
+-----------------------------+
| Call Stack |
+-----------------------------+
| Task Queue |
| (setTimeout, I/O, etc) |
+-----------------------------+
| Microtask Queue |
| (Promise.then, queueMicrotask) |
+-----------------------------+
JavaScriptはシングルスレッドのイベント駆動モデル。
Event Loopは以下の順序で実行を制御する:
- コールスタックが空になるまで同期処理を実行
- マイクロタスクキューからすべてのタスクを順番に実行
- タスクキュー(マクロタスク)から1つ取り出して実行
- 再びマイクロタスク→マクロタスクの順に繰り返す
各構成の意味と用途
✅ コールスタック(Call Stack)
- 同期処理が積み上がっていく場所
- 関数の呼び出しやスコープがここに保持される
✅ マイクロタスクキュー(Microtask Queue)
Promise.then()
MutationObserver
queueMicrotask()
→ イベントループ1サイクル内で必ず全て消化される
✅ タスクキュー(Macrotask Queue)
setTimeout()
setInterval()
-
setImmediate()
(Node.js) requestAnimationFrame()
→ 次のイベントループのタイミングで処理される
→ マイクロタスクより後に実行される
実行順の基本原則
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise');
});
console.log('script end');
✅ 出力順:
script start
script end
promise
setTimeout
→ Promise.then
は マイクロタスク → setTimeout
は マクロタスク
高度な順序制御:ネストの実例
Promise.resolve()
.then(() => {
console.log('micro 1');
setTimeout(() => console.log('macro inner'), 0);
})
.then(() => console.log('micro 2'));
→ 出力順:
micro 1
micro 2
macro inner
queueMicrotask vs setTimeout
queueMicrotask(() => console.log('micro'));
setTimeout(() => console.log('macro'), 0);
→ queueMicrotask()
の方が先に実行される
Node.jsにおける補足:process.nextTick
-
process.nextTick()
は マイクロタスクよりもさらに優先される - 極端に多用すると無限ループのような挙動になることも
設計上の判断軸:いつ何を使うべきか?
処理内容 | 適切なAPI | 理由 |
---|---|---|
非同期だけど優先順位が高い | Promise.then() |
マイクロタスクで即実行される |
フレームのタイミングに合わせたい | requestAnimationFrame() |
描画に同期させたい |
非同期処理を次回に遅延したい | setTimeout(fn, 0) |
次のイベントループまで待つ |
純粋に軽い順序制御だけしたい | queueMicrotask() |
微細な順序制御に最適 |
よくある誤解とバグ
❌ setTimeout(fn, 0) は「すぐ」実行される?
→ 実際には最低でも1イベントループ遅延する
❌ Promise.then の中で同期的に例外が飛ばない
try {
Promise.resolve().then(() => {
throw new Error('boom');
});
} catch (e) {
// ❌ ここでは捕捉されない
}
→ ✅ .catch()
をチェーンすべき
結語
Event Loopとは、JavaScriptの非同期処理を「見える化」した構造であり、
その設計を知ることは、いつ・なにが・なぜ実行されるのかを意図的に制御できるということである。
- PromiseとsetTimeoutが逆転する理由
- 非同期処理の“順序”がなぜ狂うのか
- フレームレートに合わせるにはどうすればいいのか
これらすべての問いは、Event Loopの理解によって明確に答えられる。
JavaScriptを「動かす」から「支配する」へ。
そのために最初に習得すべきは、言語の内部構造=Event Loopである。