Node.js 的异步行为依赖于事件循环(Event Loop)。事件循环由 libuv 提供,它在单线程中调度 I/O、计时器、回调和微任务,使 Node 能够以非阻塞方式处理大量并发操作。
┌─────────────────────────────────────────────────────┐
│ Node.js 应用 │
└─────────────────────┬───────────────────────────────┘
│
┌─────────────────────▼───────────────────────────────┐
│ V8 JavaScript 引擎 │
└─────────────────────┬───────────────────────────────┘
│
┌─────────────────────▼───────────────────────────────┐
│ libuv │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 事件循环 │ │ 线程池 │ │
│ │ (Event Loop)│ │(Thread Pool) │ │
│ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 文件系统 │ │ 网络 I/O │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────┘fs、net、http 等模块每一轮事件循环会依次经过以下阶段:
┌───────────────────────┐
┌─>│ timers │ setTimeout/setInterval
│ └───────────┬───────────┘
│ ┌───────────┴───────────┐
│ │ pending callbacks │ 执行延迟到下一轮循环的 I/O 回调
│ └───────────┬───────────┘
│ ┌───────────┴───────────┐
│ │ idle, prepare │ libuv 内部使用
│ └───────────┬───────────┘
│ ┌───────────┴───────────┐
│ │ poll │ 轮询新的 I/O 事件
│ └───────────┬───────────┘
│ ┌───────────┴───────────┐
│ │ check │ setImmediate
│ └───────────┬───────────┘
│ ┌───────────┴───────────┐
└──┤ close callbacks │ socket.on('close')
└───────────────────────┘执行已到期的 setTimeout 和 setInterval 回调:
setTimeout(() => {
console.log("定时器到期")
}, 100)注意: 定时器指定的延迟不是精确时间,而是最小延迟。
执行上一轮中延迟的系统回调,如 TCP 错误:
const socket = net.connect({ port: 9999 })
socket.on("error", (err) => {
// 这个回调可能在此阶段执行
})libuv 内部使用,通常不需要关注。
处理新的 I/O 事件:
const fs = require("fs")
fs.readFile("data.txt", (err, data) => {
// 这个回调在 poll 阶段执行
console.log("文件读取完成")
})poll 阶段的行为:
执行 setImmediate 回调:
setImmediate(() => {
console.log("立即执行")
})执行关闭句柄的回调:
socket.on("close", () => {
console.log("连接关闭")
})除了上述阶段,Node.js 还有两个微任务队列:
process.nextTick 队列Node.js 特有,优先级最高:
process.nextTick(() => {
console.log("nextTick")
})
Promise.resolve().then(() => {
console.log("Promise")
})
console.log("同步")
// 输出:
// 同步
// nextTick
// PromisePromise.resolve().then(() => {
console.log("Promise 微任务")
})
async function asyncDemo() {
await Promise.resolve()
console.log("await 后的代码")
}顺序不确定:
// 顺序不确定
setTimeout(() => console.log("timeout"), 0)
setImmediate(() => console.log("immediate"))setImmediate 一定先执行:
const fs = require("fs")
fs.readFile(__filename, () => {
setTimeout(() => console.log("timeout"), 0)
setImmediate(() => console.log("immediate"))
})
// 输出:
// immediate
// timeout原因: I/O 回调在 poll 阶段执行,紧接着进入 check 阶段(setImmediate),然后下一轮才到 timers 阶段(setTimeout)。
const fs = require("fs")
const cache = new Map()
function readFileCached(filename, callback) {
if (cache.has(filename)) {
// 使用 nextTick 确保回调始终异步执行
process.nextTick(() => callback(null, cache.get(filename)))
} else {
fs.readFile(filename, callback)
}
}function promisifyCallback(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
try {
fn.call(this, ...args, (err, result) => {
if (err) reject(err)
else resolve(result)
})
} catch (err) {
// 使用 nextTick 确保错误正确传播
process.nextTick(() => reject(err))
}
})
}
}// ❌ 递归调用会阻塞事件循环
function bad() {
process.nextTick(bad)
}
// ✅ 使用 setImmediate 让 I/O 有机会执行
function good() {
setImmediate(good)
}console.log("1. 同步开始")
setTimeout(() => {
console.log("2. setTimeout")
}, 0)
setImmediate(() => {
console.log("3. setImmediate")
})
process.nextTick(() => {
console.log("4. nextTick")
})
Promise.resolve().then(() => {
console.log("5. Promise")
})
console.log("6. 同步结束")
// 输出顺序:
// 1. 同步开始
// 6. 同步结束
// 4. nextTick
// 5. Promise
// 2 或 3(不确定)const fs = require("fs")
fs.readFile(__filename, () => {
console.log("1. readFile")
setTimeout(() => {
console.log("2. setTimeout")
}, 0)
setImmediate(() => {
console.log("3. setImmediate")
})
process.nextTick(() => {
console.log("4. nextTick")
})
})
console.log("0. 同步")
// 输出顺序:
// 0. 同步
// 1. readFile
// 4. nextTick
// 3. setImmediate
// 2. setTimeoutfunction heavyCompute() {
let sum = 0
for (let i = 0; i < 1e9; i++) {
sum += i
}
return sum
}
console.time("compute")
heavyCompute() // 阻塞事件循环
console.timeEnd("compute")async function heavyComputeChunked(total, chunkSize = 1e6) {
let sum = 0
for (let i = 0; i < total; i += chunkSize) {
const end = Math.min(i + chunkSize, total)
for (let j = i; j < end; j++) {
sum += j
}
// 让出控制权
await new Promise(resolve => setImmediate(resolve))
}
return sum
}const { Worker } = require("worker_threads")
function runInWorker(data) {
return new Promise((resolve, reject) => {
const worker = new Worker("./compute.js", { workerData: data })
worker.on("message", resolve)
worker.on("error", reject)
})
}
const result = await runInWorker({ n: 1e9 })libuv 默认提供 4 个线程用于处理阻塞操作:
// 查看线程池大小
console.log(process.env.UV_THREADPOOL_SIZE) // 默认 4
// 设置线程池大小(启动前设置)
process.env.UV_THREADPOOL_SIZE = 8使用线程池的操作:
const { monitorEventLoopDelay } = require("perf_hooks")
const histogram = monitorEventLoopDelay({ resolution: 20 })
histogram.enable()
setInterval(() => {
console.log("事件循环延迟:", histogram.mean / 1e6, "ms")
}, 1000)const async_hooks = require("async_hooks")
const hook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId) {
console.log("init", { asyncId, type, triggerAsyncId })
},
destroy(asyncId) {
console.log("destroy", asyncId)
}
})
hook.enable()// ❌ CPU 密集型任务阻塞事件循环
function bad() {
for (let i = 0; i < 1e9; i++) {
// 长时间计算
}
}
// ✅ 分块处理或使用 Worker Threads
async function good() {
// 分块处理
for (let i = 0; i < 1e9; i += 1e6) {
// 处理一块
await new Promise(resolve => setImmediate(resolve))
}
}// ❌ 过度使用会导致 I/O 饥饿
process.nextTick(function recursive() {
process.nextTick(recursive)
})
// ✅ 使用 setImmediate
setImmediate(function recursive() {
setImmediate(recursive)
})// 需要 I/O 后立即执行 → setImmediate
fs.readFile("data.txt", () => {
setImmediate(() => {
// 在下一个 check 阶段执行
})
})
// 需要最小延迟 → setTimeout
setTimeout(() => {
// 在 timers 阶段执行
}, 0)process.nextTick 优先级最高,其次是 Promise 微任务setImmediate 比 setTimeout(fn, 0) 更可靠process.nextTick,避免 I/O 饥饿perf_hooks 监控事件循环性能scheduler.yield() / scheduler.postTask() 是浏览器(Chromium)侧的任务调度提案 API,Node.js 并未提供 scheduler 全局对象(Node 24 实测 typeof scheduler === 'undefined')。在 Node 中让出主线程应使用既有 API:
// 让出主线程,避免长任务阻塞事件循环
async function processItems(items) {
for (const item of items) {
await new Promise(resolve => setImmediate(resolve)) // 让出控制权
processItem(item)
}
}Node.js 22+ 性能监控 API 更加完善:
import { performance, eventLoopUtilization } from 'node:perf_hooks'
// 事件循环利用率
const elu = eventLoopUtilization()
console.log(`事件循环利用率: ${(elu.utilization * 100).toFixed(2)}%`)
// 性能观察者
const obs = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.duration}ms`)
}
})
obs.observe({ entryTypes: ['measure', 'function'] })
// 测量异步函数执行时间
performance.mark('start')
await someAsyncOperation()
performance.mark('end')
performance.measure('operation', 'start', 'end')