⚡ 30 秒速记
- 事件循环协调定时器、输入输出与立即回调
poll处理输入输出,check运行setImmediatenextTick与承诺微任务不属于普通阶段队列- 顶层定时器先后别写成固定保证
- 版本、模块格式与回调来源都会影响观察顺序
Node.js 的事件循环负责把已经就绪的工作安排到脚本线程,而不是让脚本一直等待输入输出。 循环会经过不同阶段,输入输出与立即回调有各自的处理位置,阶段回调之间还涉及微任务和下一轮通知队列。分析日志顺序时,要明确从顶层还是输入输出回调开始,也要确认版本和模块格式。长计算仍会堵住循环,不能因为用了异步接口就忽略回调耗时。
版本校准: Node.js 的事件循环阶段名称相对稳定,但定时器、微任务和底层 libuv 行为会随主版本变化。回答时必须先报运行版本,再用最小脚本验证顺序;不要把浏览器事件循环或某个旧版本输出当作所有 Node.js 版本的固定结论。
# Node.js 事件循环
事件循环通俗来说就是一个无限的 while 循环。现在假设你对这个 while 循环什么都不了解,你一定会有以下疑问。
- 谁来启动这个循环过程,循环条件是什么?
- 循环的是什么任务呢?
- 循环的任务是否存在优先级概念?
- 什么进程或者线程来执行这个循环?
- 无限循环有没有终点?
带着这些问题,我们先来看看 Node.js 官网提供的事件循环原理图。
# Node.js 循环原理
图 为 Node.js 官网的事件循环原理的核心流程图。

可以看到,这一流程包含 6 个阶段,每个阶段代表的含义如下所示。
(1)timers:本阶段执行已经被 setTimeout() 和 setInterval() 调度的回调函数,简单理解就是由这两个函数启动的回调函数。
(2)pending callbacks:本阶段执行某些系统操作(如 TCP 错误类型)的回调函数。
(3)idle、prepare:仅系统内部使用,你只需要知道有这 2 个阶段就可以。
(4)poll:检索新的 I/O 事件,执行与 I/O 相关的回调,其他情况 Node.js 将在适当的时候在此阻塞。这也是最复杂的一个阶段,所有的事件循环以及回调处理都在这个阶段执行,接下来会详细分析这个过程。
(5)check:setImmediate() 回调函数在这里执行,setImmediate 并不是立马执行,而是当事件循环 poll 中没有新的事件处理时就执行该部分,如下代码所示:
const fs = require('fs');
setTimeout(() => { // 新的事件循环的起点
console.log('1');
}, 0);
setImmediate( () => {
console.log('setImmediate 1');
});
/// 将会在 poll 阶段执行
fs.readFile('./test.conf', {encoding: 'utf-8'}, (err, data) => {
if (err) throw err;
console.log('read file success');
});
/// 该部分将会在首次事件循环中执行
Promise.resolve().then(()=>{
console.log('poll callback');
});
// 首次事件循环执行
console.log('2');