Node.js事件循环是分阶段严格顺序执行的调度机制,含timers、pending callbacks、poll、check、close callbacks五阶段,微任务(含process.nextTick)在阶段切换前插队执行。

Node.js 的 Event Loop 不是简单“轮询队列”的抽象概念,而是一个有明确阶段顺序、分步执行的调度机制。理解它的关键,不是背阶段名,而是搞清每个阶段“谁先跑、谁后跑、谁插队”,以及它如何支撑非阻塞 I/O。
六个阶段按固定顺序循环执行
每次事件循环迭代,Node.js 严格按以下顺序推进(忽略内部 idle/prepare 阶段):
-
timers:执行已到期的
setTimeout和setInterval回调 - pending callbacks:处理某些系统级 I/O 操作完成后的回调(如 TCP 错误)
- poll:核心 I/O 阶段——获取新事件(如文件读完、网络响应到达),并立即执行对应回调;若无任务且有定时器待触发,可能在此阶段等待或直接跳转
-
check:执行
setImmediate()回调 -
close callbacks:执行
socket.on('close', ...)等资源关闭类回调
注意:这个顺序每轮都重来,但并非每个阶段都一定有任务可执行。
微任务总在阶段切换前“插队”
真正影响代码执行顺序的,往往不是这六个阶段本身,而是夹在它们之间的微任务队列:
立即学习“Java免费学习笔记(深入)”;
- 每个阶段执行完自己的回调后,立刻清空当前微任务队列(包括
Promise.then/catch/finally、queueMicrotask) -
process.nextTick()是特殊微任务,优先级更高——它甚至在当前阶段所有回调执行完后、微任务队列清空前就执行 - 这意味着:哪怕你在
setTimeout回调里创建 Promise,它的.then也会在本轮timers阶段结束后立刻运行,而不是等到下一轮
timers 和 poll 阶段的协作常被误解
定时器的“准时”是假象,实际受 poll 阶段影响:
-
setTimeout(fn, 100)表示“至少 100ms 后尽快执行”,但若poll阶段正忙(比如一个长耗时同步操作刚结束,或 I/O 正在阻塞),即使时间到了,timers阶段也不会立即启动 - 反过来,如果
poll队列为空,且有到期定时器,Node.js 会直接退出poll进入timers阶段;如果没有到期定时器,poll可能阻塞等待新 I/O 事件 - 因此:
setTimeout(..., 0)并不等于“马上执行”,它只是把回调放进timers队列,等待下一轮循环进入该阶段
setImmediate 和 setTimeout(0) 的执行时机差异
这是检验你是否真懂阶段顺序的关键点:
-
setImmediate()的回调一定在check阶段运行,也就是poll阶段之后 -
setTimeout(..., 0)的回调在timers阶段运行,也就是本轮循环最开头 - 但如果代码在 I/O 回调中调用两者(比如
fs.readFile的回调里),由于 I/O 属于poll阶段,此时poll已执行完毕,下一轮循环会先走timers(可能没到期),再走check,所以setImmediate反而比setTimeout(..., 0)先执行


















