Node.js的事件循环是其实现非阻塞I/O和异步处理的核心机制,由底层libuv库实现,整体运行过程被划分为六个依次执行的阶段,每个阶段都有特定的任务处理队列和回调执行规则。

事件循环六个阶段详解
1. timers阶段
这个阶段主要执行由setTimeout和setInterval设置的定时器回调。需要注意的是,定时器设定的时间并不是精确的执行时间,而是回调最早可以执行的时间阈值,只有当系统时间到达阈值且当前阶段轮到timers阶段时,对应的回调才会被执行。
// timers阶段示例
setTimeout(() => {
console.log('setTimeout回调执行');
}, 0);
// 立即执行同步代码
console.log('同步代码执行');
// 输出顺序:同步代码执行 - setTimeout回调执行2. pending callbacks阶段
这个阶段用来执行一些系统操作的回调,比如TCP连接错误、文件读取错误等底层异步操作的回调,这些回调在之前的循环中没有被处理,会放到这个阶段执行,普通用户层面的异步回调一般不会进入这个阶段。
3. idle, prepare阶段
这两个阶段是libuv内部使用的准备阶段,只在内部做一些初始化和准备工作,用户层面几乎不会直接接触到这两个阶段的执行逻辑,也不需要针对这个阶段做特殊的代码处理。
4. poll阶段
poll阶段是事件循环中非常重要的阶段,主要做两件事:一是执行I/O相关的回调,比如文件读取、网络请求等操作的回调;二是计算需要阻塞等待的时间,等待新的I/O事件到来。如果poll阶段有可执行的回调,会依次执行直到队列清空或者达到系统限制;如果没有回调,会根据之前是否有setImmediate或者定时器来决定下一步行为:如果有setImmediate回调,会进入check阶段;如果有定时器到期,会回到timers阶段执行定时器回调。
const fs = require('fs');
// I/O回调会在poll阶段执行
fs.readFile('./test.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log('文件读取完成,poll阶段执行回调');
});5. check阶段
这个阶段专门用来执行setImmediate设置的回调。setImmediate是Node.js特有的API,它的回调会在poll阶段结束后立即执行,优先级高于下一次循环的定时器回调。
setTimeout(() => {
console.log('timers阶段:setTimeout回调');
}, 0);
setImmediate(() => {
console.log('check阶段:setImmediate回调');
});
// 执行顺序不一定,因为setTimeout延迟0ms可能和setImmediate的触发时机有先后差异6. close callbacks阶段
这个阶段用来执行关闭事件的回调,比如socket.on('close', callback)或者process.on('exit', callback)这类关闭相关的回调,会在事件循环的这个阶段被执行。
阶段执行顺序总结
事件循环的六个阶段按照固定顺序依次执行,每一轮循环都会完整走完这六个阶段(如果某个阶段没有任务则跳过),然后再进入下一轮循环,直到所有任务都处理完成,进程退出。整体的顺序为:timers -> pending callbacks -> idle/prepare -> poll -> check -> close callbacks。
| 阶段名称 | 处理任务类型 | 典型API |
|---|---|---|
| timers | 定时器到期回调 | setTimeout、setInterval |
| pending callbacks | 系统操作延迟回调 | 底层错误回调 |
| idle/prepare | 内部准备任务 | 无用户层API |
| poll | I/O操作回调 | fs.readFile、http请求回调 |
| check | 立即执行回调 | setImmediate |
| close callbacks | 关闭事件回调 | socket close、process exit |
常见误区说明
很多开发者会混淆process.nextTick和事件循环阶段的关系,实际上process.nextTick的回调不属于事件循环的任何阶段,它会在当前阶段执行完成后、下一个阶段开始之前立即执行,优先级高于所有事件循环阶段的回调。而Promise.then的回调属于微任务,执行时机和process.nextTick类似,但优先级低于process.nextTick。
setTimeout(() => {
console.log('timers阶段回调');
}, 0);
process.nextTick(() => {
console.log('nextTick回调,优先级更高');
});
Promise.resolve().then(() => {
console.log('Promise then回调,优先级低于nextTick');
});
// 输出顺序:nextTick回调 - Promise then回调 - timers阶段回调