事件循环的六个阶段
Node.js 的事件循环由 libuv 驱动,分为六个阶段:
┌───────────────────────────┐
┌─>│ timers │ setTimeout, setInterval
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │ I/O 错误回调
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │ 内部使用
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ poll │ I/O 回调
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ check │ setImmediate
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
微任务优先级
微任务(microtasks)在每个阶段切换时清空:
console.log('1');
setTimeout(() => console.log('setTimeout'), 0);
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
console.log('2');
// 输出顺序:1 → 2 → nextTick → promise → setTimeout
关键规则:
- process.nextTick 优先级 > Promise 微任务
- 所有微任务在进入下一个事件循环阶段前清空
实际问题:I/O 内的 setTimeout vs setImmediate
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));
});
// 在 I/O 回调中,setImmediate 始终先于 setTimeout 执行
// 输出:setImmediate → setTimeout
避免阻塞事件循环
// ❌ 阻塞事件循环
function processLargeArray(arr) {
return arr.reduce((acc, item) => heavyCompute(acc, item), 0);
}
// ✅ 分片处理
async function processLargeArrayAsync(arr) {
let result = 0;
for (let i = 0; i < arr.length; i++) {
result = heavyCompute(result, arr[i]);
if (i % 1000 === 0) {
await new Promise(resolve => setImmediate(resolve)); // 让出控制权
}
}
return result;
}
总结
理解事件循环是写好 Node.js 代码的基础。核心要点:微任务优先于宏任务,process.nextTick 优先于 Promise,I/O 内 setImmediate 优先于 setTimeout。