logo

イベントループビジュアライザー

無料オンラインの JavaScript イベントループビジュアライザー。プリセットのコードを一歩ずつ進め、コールスタック・microtask キュー・macrotask キューの動きと、順番に出力される console を確認できます。Promise のコールバックが setTimeout より先に走る理由が最も直感的にわかります。
logo
Paji 開発ワークショップ
イベントループビジュアライザー

イベントループビジュアライザー

無料オンラインの JavaScript イベントループビジュアライザー。プリセットのコードを一歩ずつ進め、コールスタック・microtask キュー・macrotask キューの動きと、順番に出力される console を確認できます。Promise のコールバックが setTimeout より先に走る理由が最も直感的にわかります。
処理中

イベントループについて

イベントループの役割

JavaScript は単一スレッドで動くため、一度に1つのことしかできません。同期コードはコールスタック上で上から下へ実行されます。非同期のもの(setTimeout のコールバック、Promise の .then、click ハンドラー)はその場で実行されず、キューに置かれます。イベントループは「次に何を実行するか」を決める単純なルールです。コールスタックが空になるたびに、待っている次のタスクを取り出して実行し、それを繰り返します。このツールはそのループを一歩ずつ可視化します。

microtask と macrotask

実はキューは2つあり、その優先順位こそがこの謎の核心です。Promise のコールバック(と queueMicrotask)は microtask キューへ、setTimeout・setInterval・ほとんどのイベントは macrotask キューへ入ります。ルールは、各 macrotask のあと(および最初のスクリプトのあと)、ループは次の macrotask を実行する前に microtask キュー全体を排出する、というものです。だから同じ遅延なら Promise の .then は常に setTimeout に勝ちます。microtask が先に空にされるからです。このツールで2つのキューを見れば、順序はもう謎ではなくなります。

よくある質問

このテーマに関するよくある疑問と回答をまとめました。

なぜ Promise のコールバックは setTimeout より先に走るのですか?

Promise のコールバックは microtask、setTimeout のコールバックは macrotask であり、イベントループは各 macrotask のあと(および最初のスクリプトのあと)、次の macrotask を実行する前に microtask キュー全体を排出するからです。だから 0ms の setTimeout でも macrotask キューで待たされ、保留中の microtask がすべて先に走ります。

何が microtask に該当しますか?

Promise の反応(.then・.catch・.finally)、await の継続、queueMicrotask のコールバック、MutationObserver のコールバックは microtask です。setTimeout・setInterval・requestAnimationFrame・I/O・ほとんどの DOM イベントは macrotask です。この区別が重要なのは、ループが macrotask の間にすべての microtask を空にするため、microtask を延々と予約すると描画や後続のタイマーを飢餓状態にしうるからです。

0ms の setTimeout はすぐに実行されますか?

いいえ。0ms の遅延はコールバックが「できるだけ早く実行できる」ことを意味するだけで、依然として macrotask キューに入り、現在の同期コードと保留中のすべての microtask が終わるまで待ちます。ブラウザは入れ子のタイマーを最小値(数階層後で約 4ms)に制限するため、0ms は下限であって即時呼び出しではありません。