全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-15 10 分钟 ✍️ juanwangdev

Node.js 事件循环机制深度解析

事件循环决定了异步任务的执行顺序,深入理解它对性能优化和问题排查至关重要。

事件循环架构

Node.js 事件循环基于 libuv 实现,核心结构:

JavaScript
   ┌───────────────────────────┐
   ┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─>│     pending callbacks     │
│  │  └───────┬─────────────┘
│  │  ┌─>│       idle, prepare       │
│  │  │  └───────┬─────────────┘
│  │  │  ┌─>│           poll           │<─── I/O 事件
│  │  │  │  └───────┬─────────────┘
│  │  │  │  ┌─>│          check           │
│  │  │  │  │  └───────┬─────────────┘
│  │  │  │  │  ┌─>│    close callbacks    │
│  │  │  │  │  │  └───────┬─────────────┘
│  │  │  │  │  │  └───────┘
│  │  │  │  │  └─> 退出循环

六个阶段详解

timers 阶段

执行 setTimeout/setInterval 到期的回调:

JavaScript
// timers 阶段执行已到期定时器
setTimeout(() => console.log('timer'), 100);

// 定时器到期时间检查
// 在 poll 阶段计算是否有到期定时器

定时器执行时机不精确,poll 阶段阻塞时可能延迟执行。

pending callbacks 阶段

执行系统操作回调(上一轮循环未执行的 I/O 回调):

JavaScript
// TCP 错误处理
socket.on('error', (err) => {
  // 在 pending callbacks 阶段执行
});

idle, prepare 阶段

libuv 内部使用,无需关注。

poll 阶段

核心阶段,处理 I/O 事件:

JavaScript
// poll 阶段行为
// 1. 计算最长等待时间
// 2. 执行 I/O 回调
// 3. 检查是否有到期定时器

// poll 阶段阻塞规则
// - 有 I/O 回调:执行后进入 check
// - 无 I/O 回调:
//   - 有 setImmediate:进入 check
//   - 有到期定时器:进入 timers
//   - 否则:等待新 I/O 事件

check 阶段

执行 setImmediate 回调:

JavaScript
setImmediate(() => {
  // 在 check 阶段执行
});

close callbacks 阶段

执行 close 事件回调:

JavaScript
socket.destroy();
socket.on('close', () => {
  // 在 close callbacks 阶段执行
});

阶段切换机制

JavaScript
// poll 阶段决策流程
if (hasIOCallbacks) {
  executeCallbacks();
  goto check;
} else if (hasSetImmediate) {
  goto check;
} else if (hasExpiredTimers) {
  goto timers;
} else {
  waitForIO(timeout);
}

timers 与 setImmediate 执行顺序

JavaScript
// 在非 I/O 回调中(不确定顺序)
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// 顺序可能不同,取决于进程启动时机

// 在 I/O 回调中(确定顺序)
const fs = require('fs');
fs.readFile('file.txt', () => {
  setTimeout(() => console.log('timeout'), 0);
  setImmediate(() => console.log('immediate'));
});
// 输出: immediate -> timeout
// I/O 回调在 poll 阶段,下一阶段是 check

循环退出条件

JavaScript
// 事件循环退出的条件
// 1. 没有 active 定时器
// 2. 没有 active的 I/O 操作
// 3. 没有 setImmediate 待执行

// process._getActiveRequests()
// process._getActiveHandles()

事件循环监控

JavaScript
// 监控循环延迟
const { performance } = require('perf_hooks');
const timerify = performance.timerify;

function measure() {
  const start = performance.now();
  setImmediate(() => {
    const lag = performance.now() - start;
    console.log('循环延迟:', lag);
  });
}
measure();

UV_THREADPOOL_SIZE

JavaScript
// libuv 线程池大小(默认 4)
process.env.UV_THREADPOOL_SIZE = 8;

// 影响:fs、crypto、zlib 等异步操作并发度

实例分析

text
console.log('1');

setTimeout(() => {
  console.log('2');
  Promise.resolve().then(() => console.log('3'));
}, 0);

setImmediate(() => {
  console.log('4');
  Promise.resolve().then(() => console.log('5'));
});

Promise.resolve()
  .then(() => console.log('6'))
  .then(() => console.log('7'));

process.nextTick(() => console.log('8'));

console.log('9');

// 输出顺序分析:
// 1, 9 (同步)
// 8 (nextTick)
// 6, 7 (微任务)
// 2 或 4 (取决于 timers/poll)
// 3 或 5 (微任务)
// ...

要点总结

  • 事件循环分六阶段:timers → pending → idle → poll → check → close
  • poll 是核心阶段,处理 I/O 并决定下一步
  • timers 执行时机不精确,poll 阻塞会延迟
  • I/O 回调中 setImmediate 优先于 setTimeout
  • UV_THREADPOOL_SIZE 影响异步 I/O 并发度

📝 发现内容有误?点击此处直接编辑

← 上一篇 Node.js EventEmitter 事件发射器
下一篇 → Node.js 异步回调
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库