Node.js事件循环与异步编程深度解析
Node.js的核心设计理念是单线程、非阻塞I/O,这一切都建立在事件循环(Event Loop)机制之上。深入理解事件循环的工作原理,对于编写高性能的Node.js应用至关重要。本文将从事件循环的底层机制出发,结合实际案例,帮你彻底掌握Node.js异步编程。
一、事件循环的六个阶段
Node.js的事件循环分为六个主要阶段,每个阶段都有一个先进先出的回调队列:
┌───────────────────────────┐
┌─>│ timers │ 执行setTimeout/setInterval回调
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │ 执行系统级回调(如TCP错误回调)
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │ 仅内部使用
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ poll │ 获取新I/O事件,执行I/O回调
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ check │ 执行setImmediate回调
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ close callbacks │ 执行close事件回调
│ └─────────────┬─────────────┘
│ └──────────────→ 退出或有新事件时继续
二、微任务与宏任务
每个宏任务执行完后,会先清空所有微任务队列:
console.log("1 - 同步代码");
setTimeout(() => {
console.log("2 - 宏任务 setTimeout");
}, 0);
setImmediate(() => {
console.log("3 - 宏任务 setImmediate");
});
Promise.resolve().then(() => {
console.log("4 - 微任务 Promise");
});
process.nextTick(() => {
console.log("5 - 微任务 nextTick");
});
// 输出顺序:1 -> 5 -> 4 -> 2 -> 3
// nextTick优先级高于Promise
三、异步错误处理最佳实践
// 错误示范 - 吞掉Promise异常
async function bad() {
fetch("/api/data"); // 没有await,错误被吞掉
}
// 正确做法
async function good() {
try {
const response = await fetch("/api/data");
const data = await response.json();
return data;
} catch (error) {
console.error("请求失败:", error.message);
// 可以选择重试或返回默认值
return null;
}
}
// Express中的异步错误处理
app.get("/api/users", asyncHandler(async (req, res) => {
const users = await User.findAll();
res.json(users);
}));
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
四、流(Stream)处理大数据
// 读取大文件并处理
const fs = require("fs");
const readline = require("readline");
async function processLargeFile(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
let lineCount = 0;
for await (const line of rl) {
lineCount++;
// 逐行处理,不会撑爆内存
if (line.includes("ERROR")) {
console.log(`行${lineCount}: ${line}`);
}
}
console.log(`总行数: ${lineCount}`);
}
五、Worker Threads多线程
// 主线程
const { Worker } = require("worker_threads");
function runInWorker(workerData) {
return new Promise((resolve, reject) => {
const worker = new Worker("./worker.js", { workerData });
worker.on("message", resolve);
worker.on("error", reject);
worker.on("exit", (code) => {
if (code !== 0) reject(new Error(`Worker退出码: ${code}`));
});
});
}
// 使用Worker处理CPU密集型任务
const results = await Promise.all([
runInWorker({ task: "process", data: chunk1 }),
runInWorker({ task: "process", data: chunk2 }),
runInWorker({ task: "process", data: chunk3 }),
]);
理解事件循环和异步编程是成为Node.js高手的必经之路。记住核心原则:不要阻塞事件循环,将CPU密集型任务交给Worker Threads,合理使用流处理大数据,始终正确处理异步错误。这些知识将帮助你在面对复杂场景时做出正确的架构决策。