DevLog
Node.js算法

深入理解 Node.js 事件循环:从宏任务到微任务

2025-08-18·14

事件循环的六个阶段

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。

返回文章列表