process.nextTick、Promise、setImmediate 与 setTimeout 的关系
解释 Node.js 中 process.nextTick、Promise 微任务、setImmediate 和 setTimeout 的执行优先级与常见输出顺序。
#type / synthesis
#status / growing
#tech / dev / backend
#resource / nodejs
#resource / javascript
[!info] related notes
- 所属 MOC: Node.js MOC, Node.js 后端面试 MOC
- 相关概念: Node.js 事件循环阶段, libuv 事件循环与 Worker Pool, Promise, async / await
- 易混淆概念: JavaScript事件循环, JS 中的事件循环
- 面试问法: Event Loop、宏任务和微任务怎么理解
process.nextTick、Promise、setImmediate 与 setTimeout 的关系
范围
这篇只处理 Node.js 宿主里的一个高频面试混淆点:
process.nextTick- Promise 微任务
setImmediatesetTimeout(fn, 0)
它不重复解释什么是事件循环本身,基础部分看 Node.js 事件循环阶段。
为什么要放在一起理解
很多人知道“Promise 比 setTimeout 早”,但一到 Node.js 场景就会混乱,因为这里多了两个关键变量:
process.nextTicksetImmediate
如果不把四者放到同一个调度模型里,很容易把输出顺序背成死口诀。
依赖路径 / 调用链 / 演进链
先建立一个足够稳的顺序:
同步代码
↓
process.nextTick
↓
Promise 微任务
↓
事件循环中的 timers / poll / check ...
然后再记:
setTimeout(fn, 0)属于timerssetImmediate属于check
对比与易混淆点
| 项目 | 更接近什么 | 常见优先级理解 | 备注 |
|---|---|---|---|
process.nextTick | Node 宿主特有队列 | 最高 | 滥用会饿死事件循环 |
Promise.then | 标准微任务 | 低于 nextTick,高于下一轮阶段任务 | 浏览器和 Node 都有 |
setTimeout(fn, 0) | timers 阶段 | 要等进入 timers | 不是“立即执行” |
setImmediate | check 阶段 | 常在 I/O 回调后更早可见 | 不是所有场景都稳压过 setTimeout |
一个常见顺序题
console.log('start');
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
console.log('end');
更稳的分析是:
start
end
nextTick
promise
timeout / immediate(顶层主模块里别背死)
为什么 nextTick 更危险
因为它优先级太高。
如果递归调用:
function loop() {
process.nextTick(loop);
}
loop();
I/O、timer 和其他阶段任务就可能一直拿不到执行机会。
为什么 I/O 回调里经常先看到 setImmediate
因为 I/O 回调通常和 poll -> check 的切换更近。
所以在 I/O 回调里这一组代码:
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
通常更容易先看到 immediate。