FORMA

事件循环

Node.js 事件循环详解

Node.js 的事件循环是其实现非阻塞 I/O 的核心机制。它基于 libuv 库,将各种异步操作(定时器、I/O、setImmediate 等)组织成不同的阶段,每个阶段都有一个先进先出的回调队列。事件循环会按照固定的顺序反复执行这些阶段。

一、事件循环各阶段

事件循环的完整阶段顺序如下(按执行顺序排列):

阶段描述主要回调
timers执行到期的 setTimeoutsetInterval 回调。setTimeout(cb, delay)setInterval(cb, delay)
pending callbacks执行延迟到下一个循环迭代的 I/O 回调(例如某些系统错误如 ECONNREFUSED)。上一轮循环中未执行的 I/O 回调
idle, prepare仅内部使用,用于 libuv 内部操作。开发者无需关注。-
poll最重要的阶段。检索新的 I/O 事件(如文件读取、网络请求完成),执行与 I/O 相关的回调(除 timerscheckclose 以外的回调)。如果没有其他异步任务,事件循环会阻塞在此阶段等待新事件。I/O 回调(如 fs.readFilehttp.request 的回调)
check执行 setImmediate 的回调。setImmediate(cb)
close callbacks执行关闭回调,如 socket.on('close')socket.on('close')process.on('exit')

各阶段详细说明

1. timers 阶段

  • 执行所有到期的 setTimeoutsetInterval 回调。
  • 注意:实际执行延迟可能大于设定的阈值,因为事件循环可能被其他回调阻塞。

2. pending callbacks 阶段

  • 执行一些上一轮循环中未处理的 I/O 回调,通常是与网络或文件系统有关的错误回调(如 ECONNREFUSED)。

3. idle / prepare 阶段

  • 仅 libuv 内部使用,开发者编写的 JavaScript 代码不会在此阶段执行。

4. poll 阶段

  • 核心行为
    • 如果 poll 队列不为空,则同步取出队列中的回调依次执行,直到队列清空或达到系统上限。
    • 如果 poll 队列为空:
      • 若有待执行的 setImmediate 回调,则立即结束 poll 阶段,进入 check 阶段。
      • 若有即将到期的 timers,则检查定时器并跳回 timers 阶段执行到期回调。
      • 否则,事件循环会在此阶段阻塞等待新的 I/O 事件或定时器到期,以提高效率。
  • 这是 Node.js 实现高并发 I/O 的关键:绝大部分时间事件循环停留在 poll 阶段,等待新的事件。

5. check 阶段

  • 执行所有 setImmediate 回调。setImmediate 的回调会在 I/O 处理完毕后立即执行,不经过 poll 阶段的等待。

6. close callbacks 阶段

  • 执行关闭事件回调,如 socket.on('close', () => {...})

二、process.nextTicksetImmediate 的区别与执行时机

process.nextTick

  • 不属于事件循环任何一个阶段,而是一个独立的队列,在每个阶段结束后立即执行。
  • 执行时机:当前阶段的所有回调执行完毕后,在进入下一个阶段之前,会完全清空 process.nextTick 队列。
  • 因为 nextTick 会在当前阶段末尾执行,所以可被看作比任何后续阶段的回调(包括 setImmediate)具有更高的优先级。

setImmediate

  • 属于 check 阶段的回调,会在 poll 阶段之后执行。
  • 执行时机:事件循环进入 check 阶段时,清空 setImmediate 队列。

区别总结

特性process.nextTicksetImmediate
所属阶段独立队列,不属于事件循环任何阶段check 阶段
执行时机当前阶段结束后,立即执行事件循环进入 check 阶段时执行
优先级非常高(立即执行)较低(需经过 poll 阶段)
递归调用影响递归调用 nextTick 会阻塞事件循环(导致 I/O 饥饿)递归调用 setImmediate 不会阻塞,因为每次都会经过 poll 阶段
典型应用在同一个阶段内推迟执行,或强制执行某些清理操作在 I/O 完成后安排工作,或实现递归异步调用

代码示例:nextTick vs setImmediate 的执行顺序

js
setImmediate(() => console.log("setImmediate"));
process.nextTick(() => console.log("nextTick"));
console.log("同步代码");

// 输出顺序:
// 同步代码
// nextTick
// setImmediate

setTimeout(fn, 0)setImmediate 的比较

  • 如果调用时机在 I/O 循环内(比如在文件读取回调中),setImmediate 总是先于 setTimeout(fn,0) 执行。
  • 如果在主模块中直接调用,顺序取决于事件循环启动时的性能(通常 setTimeout(fn,0) 可能先执行,但不确定)。因此要依赖顺序时需使用 setImmediatenextTick

三、微任务与宏任务在 Node 中的表现

宏任务(MacroTask)

  • 在 Node.js 中,宏任务可以理解为事件循环各阶段队列中的回调。
    • setTimeoutsetInterval → timers 阶段
    • I/O 回调 → poll 阶段
    • setImmediate → check 阶段
    • close 事件 → close callbacks 阶段

微任务(MicroTask)

  • 微任务包括:
    • Promise.then/catch/finally 回调
    • queueMicrotask
    • process.nextTick(严格说它更特别,优先级高于微任务)

执行顺序规则

在 Node.js 中(以官方文档和常见实现为准),执行顺序为:

  1. 执行当前阶段的所有宏任务(如 timers 队列中的全部回调)。
  2. 清空 process.nextTick 队列
  3. 清空微任务队列(Promise、queueMicrotask)。
  4. 进入事件循环的下一个阶段。

即:nextTick > 微任务 (Promise) > 其他宏任务(下一阶段)

验证示例

js
Promise.resolve().then(() => console.log("promise1"));
process.nextTick(() => console.log("nextTick1"));
setTimeout(() => console.log("timeout"), 0);
setImmediate(() => console.log("immediate"));

console.log("sync");

// 输出顺序(Node 环境):
// sync
// nextTick1
// promise1
// timeout   (或 immediate,取决于启动时机)
// immediate (或 timeout)

注意:timeoutimmediate 的顺序在非 I/O 循环中不固定;但 nextTickpromise 的先后是确定的。

特殊说明

  • process.nextTick 的优先级高于标准微任务,这是 Node.js 的设计选择,目的是让开发者能够在当前操作完成后立即执行某些任务,而不必等到微任务队列清空。
  • 滥用 process.nextTick 会导致事件循环无法进入 poll 阶段,造成 I/O 饥饿。官方建议优先使用 setImmediatePromise

四、总结表格

类型代表 API执行阶段/时机优先级
宏任务(timers)setTimeoutsetIntervaltimers 阶段(每轮最先)低(受延迟和队列影响)
宏任务(poll)文件 I/O、网络 I/O 回调poll 阶段
宏任务(check)setImmediatecheck 阶段(poll 之后)中低
宏任务(close)socket.on('close')close callbacks 阶段(最后)最低
微任务Promise.thenqueueMicrotask当前阶段结束后、nextTick 之后
特殊队列process.nextTick当前阶段结束后、微任务之前最高

推荐参考资料

  1. Node.js 官方文档The Node.js Event Loop, Timers, and process.nextTick()
  2. libuv 文档Design overview of libuv
  3. 深入理解 Node.js 事件循环:《Node.js 设计模式》第 2 章
  4. 高性能 Node.js 最佳实践:官方博客文章 Don't Block the Event Loop

理解事件循环是编写高性能、可预测的 Node.js 应用的基础,尤其是在处理大量 I/O 或需要精细控制异步任务顺序时。

相关文章