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 是下限,不是立即调用。