事件循环是怎么工作的
What does the event loop
看答案Show answer
一句话:JS只有一个主线程,事件循环负责在「调用栈空了」的时候,从任务队列里取下一个任务放上去执行。
完整的一轮(这段是标准答案):
- 执行完当前的同步代码(调用栈清空)。
- 把微任务队列全部清空—— 注意是「全部」,而且清微任务时新产生的微任务也在这一轮里执行完。
- (浏览器)需要的话渲染一帧。
- 取一个宏任务执行,回到第 2 步。
谁是微任务:Promise.then/catch/finally、await 之后的代码、queueMicrotask、MutationObserver。
谁是宏任务:setTimeout / setInterval、DOM 事件回调、网络回调、requestAnimationFrame(严格说它在渲染前,单独一档)。
一句话记住优先级:Promise 一定比setTimeout 先跑,即使 setTimeout(…, 0)。
会追问:「异步是谁做的?」—— 不是引擎,是宿主环境(浏览器的 Web API / Node 的 libuv)。引擎只管执行 JS。这条和 #276 是一组。
「setTimeout(fn, 0)真的 0 毫秒吗?」—— 不是「立刻」:它要等当前同步代码和全部微任务跑完、主线程空出来才轮到;另外定时器嵌套超过 5 层后,浏览器会把延迟钳到至少 4ms。所以它只是「尽快,但不是现在」。
Node 的差别(问到就是加分):Node 的宏任务分了六个阶段(timers / pending / poll / check / close…),setImmediate 在 check 阶段;CommonJS 里 process.nextTick 队列先于 Promise 微任务,ES 模块的顶层代码里则是 Promise 先执行。
In one line: JS has one main thread, and the event loop’s job is to pull the next task off a queue and put it on the stack whenever the call stack goes empty.
One full turn — this part is the model answer:
- Run the synchronous code to the end (the call stack empties).
- Drain the microtask queue completely — note “completely”: microtasks queued while draining also run inside this same turn.
- (In a browser) paint a frame if one is needed.
- Take one macrotask, run it, go back to step 2.
Microtasks: Promise.then/catch/finally, the code after an await, queueMicrotask, MutationObserver.
Macrotasks: setTimeout / setInterval, DOM event handlers, network callbacks, requestAnimationFrame (strictly it runs just before paint, in a class of its own).
The priority in one line: a Promise always runs before a setTimeout, even setTimeout(…, 0).
Follow-up: “Who actually does the async work?” — not the engine, the host environment (the browser’s Web APIs, libuv in Node). The engine only runs JS. This one pairs with #276.
“Is setTimeout(fn, 0) really zero milliseconds?” — not “right now”: it waits until the synchronous code and every microtask have finished and the main thread is free; and once timers are nested more than five levels deep, browsers clamp the delay to at least 4ms. So it means “as soon as possible, but not now”.
How Node differs (a bonus point if it comes up): Node splits macrotasks into six phases (timers / pending / poll / check / close…), setImmediate lands in the check phase, and in CommonJS the process.nextTick queue runs before the Promise microtasks, while in the top-level code of an ES module the Promises run first.