事件循环是怎么工作的
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 毫秒吗?」—— 不是,浏览器最小约 4ms, 而且要等主线程空闲。所以它只是「尽快,但不是现在」。
Node 的差别(问到就是加分): Node 的宏任务分了六个阶段 (timers / pending / poll / check / close…),setImmediate 在 check 阶段,process.nextTick比所有微任务都优先。
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?” — no. Browsers clamp it to roughly 4ms, and it still waits for a free main thread. 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 process.nextTick jumps ahead of every microtask.