Node.js事件循环与异步编程深度解析


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,合理使用流处理大数据,始终正确处理异步错误。这些知识将帮助你在面对复杂场景时做出正确的架构决策。


0.079920s