← 返回目录

第三章:异步编程与错误处理

理解 JavaScript 的事件驱动本质

🆚 与 Java 的对比:Java 用线程池处理并发,TS/Node.js 用单线程事件循环。Java 的 CompletableFuture 类似 Promise,Java 21 的虚拟线程更接近 async/await 的体验。
🆚 与 Go 的对比:Go 用 goroutine + channel,TS 用 async/await + Promise。Go 的 error 返回值模式可以在 TS 中用 Result 类型模拟。

1. 为什么异步?

Node.js 采用单线程 + 事件循环模型。与 Java/C++ 的多线程不同,Node.js 不为每个请求创建线程,而是通过非阻塞 IO 和回调来处理并发:

❌ 多线程模型的挑战

  • • 线程创建和上下文切换开销大
  • • 共享内存需要锁,易死锁
  • • 调试困难,竞态条件难复现

✅ 事件循环的优势

  • • 单线程无锁,代码更简单
  • • 非阻塞 IO,高并发低开销
  • • 特别适合 IO 密集型应用

对 CPU 密集型任务,Node.js 可通过 Worker Threads 利用多核;但异步模型本身主要解决 IO 等待问题。

2. 回调函数

回调是最原始的异步模式——将一个函数作为参数传递,在异步操作完成时调用:

import { readFile } from "fs";

// Node.js 风格回调:第一个参数是 error
readFile("config.json", "utf-8", (err, data) => {
    if (err) {
        console.error("读取失败:", err.message);
        return;
    }
    console.log("文件内容:", data);
});

// 回调地狱(Callback Hell)——嵌套回调导致代码不可维护
readFile("a.txt", "utf-8", (err, a) => {
    if (err) return;
    readFile("b.txt", "utf-8", (err, b) => {
        if (err) return;
        readFile("c.txt", "utf-8", (err, c) => {
            if (err) return;
            console.log(a + b + c); // 三层嵌套,持续恶化...
        });
    });
});

3. Promise

Promise 表示一个异步操作的最终结果,有三种状态:pending(进行中)、fulfilled(成功)、rejected(失败)。

// 创建 Promise
function readFileAsync(path: string): Promise<string> {
    return new Promise((resolve, reject) => {
        readFile(path, "utf-8", (err, data) => {
            if (err) reject(err);
            else resolve(data);
        });
    });
}

// 链式调用——避免回调嵌套
readFileAsync("config.json")
    .then(data => JSON.parse(data))
    .then(config => {
        console.log("端口:", config.port);
        return config.port;
    })
    .catch(err => console.error("失败:", err.message))
    .finally(() => console.log("操作完成"));

// Promise 的类型标注
const numPromise: Promise<number> = Promise.resolve(42);
const strPromise: Promise<string> = new Promise(resolve => {
    setTimeout(() => resolve("done"), 1000);
});

4. async / await

async/await 是 Promise 的语法糖,让异步代码看起来像同步代码:

// async 函数始终返回 Promise
async function loadConfig(): Promise<Record<string, unknown>> {
    const data = await readFileAsync("config.json");
    return JSON.parse(data);
}

// 用 try/catch 处理错误
async function main(): Promise<void> {
    try {
        const config = await loadConfig();
        console.log("配置:", config);
    } catch (err) {
        console.error("加载失败:", err);
    }
}

// 从回调 → Promise → async/await 的演进
// 回调: readFile(path, cb)
// Promise: readFileAsync(path).then(data => ...)
// async:   const data = await readFileAsync(path)

// 并行执行多个 await
async function loadAll(): Promise<void> {
    // ✗ 串行——慢
    const a = await fetchUser();
    const b = await fetchOrders();

    // ✓ 并行——快
    const [user, orders] = await Promise.all([
        fetchUser(),
        fetchOrders()
    ]);
}

5. Promise 组合器

const tasks = [fetchA(), fetchB(), fetchC()];

// Promise.all —— 全部成功才成功,一个失败就失败
const [a, b, c] = await Promise.all(tasks);
// 类型自动推断为各 Promise 的结果类型元组

// Promise.allSettled —— 等待全部完成,不管成功失败
const results = await Promise.allSettled(tasks);
results.forEach(r => {
    if (r.status === "fulfilled") console.log("成功:", r.value);
    else console.log("失败:", r.reason);
});

// Promise.race —— 返回最先完成的(无论成功失败)
const fastest = await Promise.race([
    fetchFromCDN(),
    fetchFromOrigin()
]);

// Promise.any —— 返回最先成功的,全部失败才失败
const firstSuccess = await Promise.any([
    fetchFromCDN(),
    fetchFromOrigin()
]);

选择指南:all 适合全部必须成功;allSettled 适合容忍部分失败;race 适合超时控制;any 适合多源冗余。

6. 事件循环

理解事件循环是掌握 Node.js/浏览器异步行为的关键:

console.log("1: 同步");

setTimeout(() => console.log("2: 宏任务 (setTimeout)"), 0);

Promise.resolve().then(() => console.log("3: 微任务 (Promise)"));

console.log("4: 同步");

// 输出顺序: 1 → 4 → 3 → 2

执行顺序规则:

  1. 同步代码——立即执行,进入调用栈
  2. 微任务(Promise.then、queueMicrotask)——当前同步代码执行完毕后立即执行
  3. 宏任务(setTimeout、setInterval、IO 回调)——在所有微任务执行完后,从宏任务队列取一个执行
  4. 每个宏任务执行后,再次清空微任务队列,循环往复

7. 错误处理模式

// 自定义错误类
class AppError extends Error {
    constructor(
        message: string,
        public code: string,
        public statusCode: number = 500
    ) {
        super(message);
        this.name = "AppError";
    }
}

// catch 中 error 类型默认是 unknown,需要缩窄
async function fetchData(url: string): Promise<unknown> {
    try {
        const res = await fetch(url);
        if (!res.ok) throw new AppError("请求失败", "FETCH_ERROR", res.status);
        return await res.json();
    } catch (err: unknown) {
        if (err instanceof AppError) {
            console.error(`[${err.code}] ${err.message}`);
        } else if (err instanceof Error) {
            console.error("未知错误:", err.message);
        } else {
            console.error("非标准错误:", err);
        }
        throw err;
    }
}

// Result 模式——类似 Go/Rust 的错误处理(无需 try/catch)
type Result<T, E = Error> =
    | { ok: true; value: T }
    | { ok: false; error: E };

async function safeFetch(url: string): Promise<Result<unknown>> {
    try {
        const res = await fetch(url);
        const data = await res.json();
        return { ok: true, value: data };
    } catch (err) {
        return { ok: false, error: err instanceof Error ? err : new Error(String(err)) };
    }
}

// 使用 Result
const result = await safeFetch("/api/users");
if (result.ok) {
    console.log("数据:", result.value);
} else {
    console.error("错误:", result.error.message);
}

📝 本章要点