Node.js事件循环由timers、pending callbacks、idle/prepare、poll、check、close callbacks六个严格顺序阶段构成,每阶段处理特定回调;阶段切换时先清空process.nextTick队列,再清空Promise等微任务队列,确保微任务总早于下一阶段宏任务执行。

Node.js 事件循环不是简单的“定时器→I/O→立即执行”线性流程,而是六个严格顺序执行的阶段构成的循环体,每个阶段处理特定类型的回调。它的核心目标是让单线程 JavaScript 高效调度异步 I/O,不靠多线程,而靠阶段化、队列化、非阻塞的协作机制。
Timers 阶段:定时器回调的入口,但不保证准时
这是事件循环每轮的起点,专门检查并执行已到期的 setTimeout 和 setInterval 回调。
- 它只看“是否达到或超过设定时间”,不精确到毫秒级;比如设了
setTimeout(fn, 0),实际执行可能延迟几毫秒甚至更久,取决于前一阶段耗时和 Poll 队列是否积压 - 如果当前轮次 Timers 队列为空,事件循环不会等待,直接进入下一阶段
- 注意:
setImmediate(() => {})不在此阶段执行,它属于 Check 阶段
Poll 阶段:I/O 的心脏,决定是否阻塞
Poll 是最核心也最容易被误解的阶段,它既处理 I/O 完成回调(如文件读取、HTTP 响应),又控制事件循环是否“停住等待”。
- 若 Poll 队列不为空:同步逐个执行回调,直到队列清空或达到系统限制(防止饿死其他阶段)
- 若 Poll 队列为空:
- 有 setImmediate 待执行 → 立即退出 Poll,跳转到 Check 阶段
- 无 setImmediate,但有已到期的定时器 → 不阻塞,直接回到 Timers 阶段执行
- 既无 setImmediate 也无到期定时器 → 事件循环会在此阶段阻塞,等待新 I/O 事件到来(如网络包抵达、文件读完)
Check 阶段:专为 setImmediate 设计的“本轮末班车”
这个阶段唯一任务就是执行 setImmediate 注册的回调,且只在 Poll 阶段结束后、Close Callbacks 开始前运行。
使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景: (1) 暂存更改时需要提交信息; (2) 审查 diff 时希望获得结构化摘要; (3) 编写 PR 描述或变更日志(changelog)条目; (4) 用户明确要求“为本次更改编写提交信息”。 该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。
立即学习“Java免费学习笔记(深入)”;
- 它比 setTimeout(..., 0) 更稳定:只要 Poll 结束,Check 就一定会被执行(除非被 process.nextTick 或微任务抢占)
- 常见误区:setImmediate 不是“立刻执行”,而是“本轮 Poll 完后立刻执行”,所以它总晚于同一轮中的同步代码和微任务
- 典型用途:需要确保 I/O 操作完成后再做清理或后续逻辑,又不想受定时器精度干扰时使用
其他关键阶段与协同规则
除了上述三者,完整事件循环还包括:
- Pending callbacks:处理上一轮遗留的系统级错误回调,如 TCP 连接拒绝(ECONNREFUSED)
- Idle/Prepare:纯内部阶段,用户代码无法干预
-
Close callbacks:执行资源关闭事件,如
socket.on('close', ...) -
微任务优先级更高:Promise.then、catch、finally 及
queueMicrotask在每个阶段结束时立即清空,因此总早于下一阶段的宏任务(如 setTimeout、setImmediate) - process.nextTick 是特例:它不属于任何阶段,而是在当前操作结束后、进入下一阶段前强制插入执行,优先级高于所有微任务
理解这些阶段的关键,在于记住它们不是并发运行,而是串行轮转;每个阶段只处理自己队列里的任务,且微任务像“插队者”一样在每阶段尾部清空。这种设计让 Node.js 在单线程下既能响应快(微任务)、又能扛住高 I/O(Poll 阻塞可控),还不用操心线程安全。

















