setImmediate 在 I/O 回调中总先于 setTimeout(fn, 0) 执行;顶层调用时顺序不确定;nextTick 和 Promise 属微任务,优先级高于二者。

在 Node.js 中,setImmediate 和 setTimeout(fn, 0) 看似都“马上执行”,但实际谁先谁后,不能靠猜——得看它们落在事件循环的哪个阶段。
Event Loop 的六个关键阶段
Node.js 的事件循环不是线性流水线,而是按固定顺序轮转的六阶段循环:
-
Timers:执行
setTimeout和setInterval到期的回调(即使设为 0,也会被系统强制调整为 ≥1ms) - Pending callbacks:处理上一轮遗留的系统级 I/O 错误等低层回调
-
Poll:最核心阶段——检索并执行 I/O 回调(如
fs.readFile、net.createServer完成后的回调);若无任务且有setImmediate待执行,会提前退出此阶段 -
Check:专门执行
setImmediate回调 -
Close callbacks:触发
socket.on('close')等关闭事件 - Idle/Prepare:内部使用,开发者无需关注
I/O 回调内调用时,setImmediate 总是优先
这是最稳定、可预测的场景。例如文件读取完成后的回调中同时注册两者:
fs.readFile(__filename, () => {
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
});
输出一定是:
immediate
timeout
原因很清晰:I/O 回调在 Poll 阶段执行 → 紧接着进入 Check 阶段(触发 setImmediate)→ 下一轮循环才回到 Timers 阶段(触发 setTimeout)。
主模块顶层调用时,顺序不确定
当两者直接写在脚本最外层(非任何回调内):
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
输出可能是 timeout → immediate,也可能是 immediate → timeout。
这是因为:
- Node.js 启动后可能尚未进入第一轮 Timers 阶段,此时
setImmediate的 Check 阶段反而更早被轮到 - 也可能主线程刚初始化完就立刻检查 Timers 队列,导致
setTimeout先触发 - 系统负载、计时器精度等细微因素都会影响首次调度时机
别混淆:nextTick 和 Promise 是另一类机制
process.nextTick() 和 Promise.then() 属于微任务(microtask),它们不参与上述六阶段排序,而是在每个阶段结束后、进入下一阶段前立即清空执行。
所以它们的优先级高于 setImmediate 和 setTimeout(二者都是宏任务)。例如:
setTimeout(() => console.log('timer'), 0);
setImmediate(() => console.log('immediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
输出顺序恒为:
nextTick → promise → (immediate 或 timer,取决于上下文)

















