Node.js 核心事件循环机制与异步 I/O 深度剖析
很多初学者容易将浏览器环境的 Event Loop 与 Node.js 的事件循环混为一谈。实际上,Node.js 底层依靠 libuv 实现了更为精密的多阶段事件轮询。
一、 Node.js 事件循环的六大执行阶段
libuv 在每个 Tick 循环中按顺序经历以下阶段:
- Timers 阶段 :执行
setTimeout()和setInterval()的回调。 - Pending callbacks 阶段:执行延迟到下一个循环迭代的 I/O 回调(如系统错误)。
- Idle, prepare 阶段:仅内部使用。
- Poll 阶段:检索新的 I/O 事件;执行与 I/O 相关的回调;处理到期的 timers。
- Check 阶段 :专门执行
setImmediate()的回调。 - Close callbacks 阶段 :执行套接字关闭等回调,例如
socket.on('close', ...)。
二、 process.nextTick 与 setImmediate 的本质差异
process.nextTick()不属于任何 libuv 阶段,它属于 Node.js 自身的 Microtask Queue 。每次阶段切换或当前同步操作完成后,都会优先清空nextTick队列。setImmediate()则在 Check 阶段 执行,设计初衷就是为了在当前轮询结束后立即执行。
javascript
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('2: Timers 阶段');
}, 0);
setImmediate(() => {
console.log('1: Check 阶段 (优先于 Timers 执行)');
});
process.nextTick(() => {
console.log('0: Microtask nextTick 优先执行');
});
});
三、 总结与最佳实践
- 避免在 nextTick 中进行递归或重量级计算,否则会导致事件循环被饥饿卡死(Starvation)。
- 在异步回调内需排队后续操作时,优先使用
setImmediate保证让出 I/O 控制权。