Node.js 事件循环机制详解:六大阶段与微任务执行时机
Node.js 事件循环包含六个阶段,按顺序执行以处理异步回调。同步代码执行完毕后先清空微任务,随后依次进入 timers、pending callbacks、poll、check 等阶段。微任务在同步代码结束及阶段切换前执行,nextTick 优先级高于 Promise。本文详解各阶段功能、微任务执行时机及与浏览器的差异。

Node.js 六大事件队列概述
与浏览器仅包含宏任务和微任务两个队列不同,Node.js 环境中存在六个特定的事件队列,timers、pending callbacks,idle prepare、poll,check 以及 close callbacks。这些队列中存储的内容均为回调函数 callback,用于处理各类异步操作。
这六个队列严格按照固定的顺序依次执行,每个队列负责处理特定类型的任务,确保异步操作的有序调度。
timer阶段专门存放 setTimeout 和 setInterval 的回调函数,处理定时器相关的异步请求。pending callback阶段负责执行操作系统的底层回调,例如处理 TCP 和 UDP 网络通信相关的 I/O 操作。idle和prepare这两个阶段仅在系统内部使用,通常用于内部状态管理,一般开发者在日常开发中无需直接操作。poll阶段执行与 I/O 相关的回调操作,这是事件循环中处理大多数异步 I/O 请求的关键阶段。check阶段专门用于执行 setImmediate 的回调函数,处理需要在当前 I/O 阶段结束后立即执行的逻辑。close callbacks阶段负责执行 close 事件的回调,例如处理连接关闭时的清理工作。
同步代码与微任务执行机制
在 Node.js 中,代码从上到下同步执行。在执行过程中,不同的异步任务会被添加到相应的队列中:例如 setTimeout 被加入 timers 队列,文件读写操作被加入 poll 队列。当所有同步代码执行完毕后,系统会先执行满足条件的微任务。可以想象存在一个独立的微任务队列,它与上述六个宏任务队列相互独立。
当同步代码执行完成后,系统会立即执行微任务队列中的任务。一旦所有微任务执行完毕,事件循环将按照前述六个队列的顺序,依次执行各队列中满足条件的宏任务。
事件循环首先执行 timers 阶段中满足条件的宏任务。当 timers 阶段的任务全部执行完毕后,进入队列切换环节。在切换到下一个阶段之前,系统会先清空微任务列表中的微任务,确保微任务优先于后续阶段的宏任务执行。
因此,微任务的执行有两个关键时机:第一个时机是所有的同步代码执行完毕后;第二个时机是在每个事件队列切换之前。
需要注意的是,在微任务中,nextTick 的执行优先级要高于 Promise,这一特性需要特别注意,建议通过实践记忆。
setTimeout(() => {
console.log('s1');
})
Promise.resolve().then(() => {
console.log('p1');
})
console.log('start');
process.nextTick(() => {
console.log('tick');
})
setImmediate(() => {
console.log('st');
})
console.log('end');
// start end tick p1 s1 stsetTimeout(() => {
console.log('s1');
Promise.resolve().then(() => {
console.log('p1');
})
process.nextTick(() => {
console.log('t1');
})
})
Promise.resolve().then(() => {
console.log('p2')
})
console.log('start');
setTimeout(() => {
console.log('s2');
Promise.resolve().then(() => {
console.log('p3');
})
process.nextTick(() => {
console.log('t2');
})
})
console.log('end');
// start end p2 s1 s2 t1 t2 p1 p3Node.js 与浏览器事件循环的差异
Node 的事件循环机制与浏览器的事件环执行存在显著差异,主要体现在队列数量、执行时机及优先级上。
首先,任务队列的数量不同:浏览器通常只有宏任务和微任务两个队列,而 Node.js 中除了微任务队列外,还包含六个特定的事件队列,结构更为复杂。
其次,微任务的执行时机不同。两者在同步任务执行完毕后都会检查微任务队列,但浏览器在每完成一个宏任务后就会清空微任务队列;而在 Node.js 中,只有在事件队列切换时才会去清空微任务队列。
最后,在 Node.js 平台下,微任务的执行具有明确的优先级:nextTick 优先于 Promise.then,而浏览器中的微任务则遵循先进先出(FIFO)的原则。
setTimeout(() => {
console.log('timeout');
})
setImmediate(() => {
console.log('immdieate');
})定时器与立即执行回调的执行顺序分析
在 Node.js 中,有时先输出 timeout,有时先输出 immdieate。这是因为 setTimeout 需要接收一个时间参数,如果未指定则默认为 0。然而,无论 Node.js 还是浏览器,程序的实际执行时间不可能真正为 0,它会受到操作系统调度、系统负载等多种因素的影响,具体取决于运行环境。
如果 setTimeout 先执行,任务会被放入 timers 队列,此时 timeout 会先输出;如果 setTimeout 因某些原因延迟执行,那么 check 队列中的 immdieate 就会先执行。这就是为什么输出结果时而为 timeout,时而为 immdieate 的原因。
const fs = require('fs');
fs.readFile('./a.txt', () => {
setTimeout(() => {
console.log('timeout');
}, 0)
setImmediate(() => {
console.log('immdieate');
})
})在特定场景下,会一直先输出 immdieate 后输出 timeout。这是因为代码执行时,会在 timers 中加入 timeout,在 poll 中加入 fs 的回调,在 check 中加入 immdieate。当 fs 的回调执行结束后,仍处于 poll 队列。在队列切换时,系统首先检查微任务,若此处无微任务,则继续向下执行。由于此时尚未进入 check 队列,poll 清空后会直接切换到 check 队列,从而执行 immediate 回调,导致先输出 immdieate 后输出 timeout。
到此这篇关于 Node.js 事件循环的文章就介绍到这了,更多相关 Node.js 事件循环内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!
Java和JavaScript应用场景对比:企业级后端 vs Web前端与Node.js
2026-11-04 16:42:01
JavaScript模块化开发入门:CommonJS与ES Module的区别
2026-11-03 23:31:34
怎么创建并运行第一个js文件
2026-11-03 21:49:02
JavaScript运行环境怎么搭建?Node.js官方下载与安装教程
2026-11-03 20:03:04
NestJS 集成 TypeORM 或 Prisma 实现数据库持久化操作
2026-11-03 18:56:13
