logo

事件迴圈視覺化

免費線上 JavaScript 事件迴圈視覺化工具。逐步走過預設程式,看呼叫堆疊、microtask queue 與 macrotask queue 怎麼推進,console 依序印出——最直觀看懂為何 Promise 回呼比 setTimeout 先跑。
logo
啪唧開發工坊
事件迴圈視覺化

事件迴圈視覺化

免費線上 JavaScript 事件迴圈視覺化工具。逐步走過預設程式,看呼叫堆疊、microtask queue 與 macrotask queue 怎麼推進,console 依序印出——最直觀看懂為何 Promise 回呼比 setTimeout 先跑。
處理中

關於事件迴圈

事件迴圈在做什麼

JavaScript 跑在單一執行緒上,一次只能做一件事。同步程式在呼叫堆疊上由上而下執行。任何非同步的東西——setTimeout 回呼、Promise .then、click handler——都不會就地執行,而是被放進佇列。事件迴圈就是決定「下一個跑什麼」的簡單規則:只要呼叫堆疊一空,就取出下一個等待中的任務執行,然後重複。這個工具把這個迴圈一步一步變得看得見。

microtask 與 macrotask

其實有兩個佇列,而它們的優先順序正是整個謎題的關鍵。Promise 回呼(與 queueMicrotask)進 microtask queue;setTimeout、setInterval 與多數事件進 macrotask queue。規則是:每個 macrotask 之後(以及最初的 script 之後),事件迴圈會先清空整個 microtask queue,才執行下一個 macrotask。這就是為什麼相同延遲下,Promise .then 永遠贏過 setTimeout——microtask 會先被排乾。看這個工具裡的兩個佇列,順序就不再是謎。

常見問題

關於這個主題的常見疑問與實用解答。

為什麼 Promise 回呼比 setTimeout 先跑?

因為 Promise 回呼是 microtask、setTimeout 回呼是 macrotask,而事件迴圈在每個 macrotask 之後(以及最初 script 之後)會先清空整個 microtask queue,才執行下一個 macrotask。所以即使 setTimeout 設 0ms,也得在 macrotask queue 裡等,讓所有待處理的 microtask 先跑。

哪些東西算 microtask?

Promise 的反應(.then、.catch、.finally)、await 的後續、queueMicrotask 的回呼、MutationObserver 的回呼都是 microtask。setTimeout、setInterval、requestAnimationFrame、I/O 與多數 DOM 事件是 macrotask。這個區分重要,因為事件迴圈會在 macrotask 之間清空所有 microtask,所以若你無止盡地排 microtask,可能餓死畫面渲染或稍後的計時器。

setTimeout 設 0ms 會立刻執行嗎?

不會。0ms 延遲只代表回呼「盡快可以跑」,但它仍會進 macrotask queue,等到目前的同步程式與所有待處理 microtask 都結束才執行。瀏覽器還會把巢狀計時器夾到最小值(幾層之後約 4ms),所以 0ms 是下限,不是立即呼叫。