Node.js 以其单线程、异步非阻塞 I/O 的特性在高并发场景中广泛应用。然而,许多开发者对其事件循环(Event Loop) 机制不够熟悉,导致在编写异步代码时遇到回调地狱、Promise 处理不当、性能瓶颈等问题。
本文将详细解析Node.js 事件循环的运行原理,结合代码示例,帮助你深入理解其核心机制。
Node.js 运行在 V8 引擎之上,采用事件驱动(Event-Driven)+ 非阻塞 I/O,使其能在单线程环境下高效处理并发任务。
当我们运行一个 Node.js 程序时,它的执行过程大致如下:
事件循环主要由 6 个阶段组成,每个阶段执行不同类型的任务:
阶段 | 任务类型 | 执行顺序 |
---|---|---|
Timers | setTimeout 、setInterval |
1️⃣ |
I/O callbacks | 处理 I/O 任务,如 fs.readFile |
2️⃣ |
Idle, prepare | 内部使用,开发者无感知 | 3️⃣ |
Poll | 轮询 I/O 事件(如网络请求) | 4️⃣ |
Check | setImmediate 回调 |
5️⃣ |
Close callbacks | process.on('close') 处理 |
6️⃣ |
Node.js 任务分为两大类:
Promise.then()
、process.nextTick()
setTimeout
、setImmediate
、I/O 操作等console.log(' 同步代码 1')
setTimeout(() => {
console.log(' setTimeout 宏任务')
}, 0)
Promise.resolve().then(() => {
console.log(' Promise 微任务')
})
process.nextTick(() => {
console.log('⚡ process.nextTick 微任务')
})
console.log(' 同步代码 2')
同步代码 1
同步代码 2
⚡ process.nextTick 微任务
Promise 微任务
setTimeout 宏任务
结论:
- 同步任务 先执行
- process.nextTick 优先级最高
- Promise 紧随其后
- setTimeout 最后执行(属于宏任务)
Node.js 事件循环在每个阶段结束后,都会先检查并执行所有的微任务,然后再进入下一个宏任务阶段。
process.nextTick()
vs Promise.then()
process.nextTick()
具有更高的优先级,比 Promise.then()
更早执行:
setTimeout(() => {
console.log(' setTimeout')
}, 0)
Promise.resolve().then(() => {
console.log(' Promise.then')
})
process.nextTick(() => {
console.log('⚡ process.nextTick')
})
执行结果
⚡ process.nextTick
Promise.then
setTimeout
setImmediate()
vs setTimeout(0)
setImmediate()
和 setTimeout(0)
在不同情况下执行顺序不同:
setImmediate(() => console.log(' setImmediate'))
setTimeout(() => console.log('⏳ setTimeout'), 0)
setTimeout(0)
先执行setImmediate()
先执行在 Node.js 中,文件操作、网络请求等 I/O 任务被推入Poll 阶段,等待数据返回后再执行回调。
const fs = require('fs')
fs.readFile(__filename, () => {
console.log(' fs.readFile 回调')
})
setImmediate(() => {
console.log(' setImmediate')
})
setTimeout(() => {
console.log('⏳ setTimeout')
}, 0)
执行结果
fs.readFile 回调
setImmediate
⏳ setTimeout
解析
fs.readFile
属于 I/O 任务,进入 Poll
阶段setImmediate
优先执行setTimeout(0)
需要等待 下一个事件循环使用 Promise + async/await 代替回调:
const fs = require('fs').promises
async function readFile() {
try {
const data = await fs.readFile('test.txt', 'utf-8')
console.log(' 读取内容:', data)
} catch (error) {
console.error('❌ 读取失败:', error)
}
}
readFile()
使用 队列 + 批量处理 避免阻塞:
const tasks = [task1, task2, task3]
async function processTasks() {
for (const task of tasks) {
await task()
}
}
setImmediate()
适用于需要尽快执行的异步任务:
function immediateTask() {
setImmediate(() => console.log('✅ 任务已执行'))
}
immediateTask()
关键点 | 说明 |
---|---|
事件循环 | 由 6 个阶段组成,每个阶段处理不同任务 |
微任务优先 | process.nextTick() > Promise.then() |
setImmediate vs setTimeout(0) | I/O 后 setImmediate 优先,否则 setTimeout(0) 优先 |
避免回调地狱 | 使用 Promise 和 async/await |
I/O 任务优化 | 批量处理,减少阻塞 |
深入理解 Node.js 事件循环,可以帮助开发者更好地优化代码,提升系统性能,避免潜在的性能问题和 Bug。
希望这篇文章对你有所帮助!