← Back to Index

Chapter 3: Async Programming and Error Handling

Understanding JavaScript's event-driven nature

🆚 Comparison with Java: Java uses thread pools for concurrency, while TS/Node.js uses a single-threaded event loop. Java's CompletableFuture is similar to Promise, and Java 21's virtual threads are closer to the async/await experience.
🆚 Comparison with Go: Go uses goroutine + channel, TS uses async/await + Promise. Go's error return value pattern can be simulated in TS with the Result type.

1. Why Async?

Node.js uses a single-threaded + event loop model. Unlike Java/C++'s multithreading, Node.js doesn't create a thread for each request, but handles concurrency through non-blocking IO and callbacks:

❌ Challenges of Multi-threading

  • • High overhead for thread creation and context switching
  • • Shared memory needs locks, prone to deadlock
  • • Difficult to debug, race conditions hard to reproduce

✅ Advantages of Event Loop

  • • Single-threaded lockless, simpler code
  • • Non-blocking IO, high concurrency with low overhead
  • • Especially suitable for IO-intensive applications

For CPU-intensive tasks, Node.js can leverage multiple cores via Worker Threads; but the async model itself mainly solves IO waiting problems.

2. Callback Functions

Callbacks are the most primitive async pattern — passing a function as a parameter to be called when the async operation completes:

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

A Promise represents the eventual result of an async operation, with three states: pending, fulfilled (success), and rejected (failure).

// 创建 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 is syntactic sugar for Promises, making async code look like synchronous code:

// 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 Combinators

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()
]);

Selection guide: all for when all must succeed; allSettled for tolerating partial failures; race for timeout control; any for multi-source redundancy.

6. Event Loop

Understanding the event loop is key to mastering Node.js/browser async behavior:

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

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

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

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

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

Execution Order Rules:

  1. Synchronous code — executes immediately, enters the call stack
  2. Microtasks (Promise.then, queueMicrotask) — execute immediately after current synchronous code completes
  3. Macrotasks (setTimeout, setInterval, IO callbacks) — after all microtasks complete, take one from the macrotask queue
  4. After each macrotask, clear the microtask queue again, repeat the cycle

7. Error Handling Patterns

// 自定义错误类
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);
}

📝 Chapter Summary