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:
- Synchronous code — executes immediately, enters the call stack
- Microtasks (Promise.then, queueMicrotask) — execute immediately after current synchronous code completes
- Macrotasks (setTimeout, setInterval, IO callbacks) — after all microtasks complete, take one from the macrotask queue
- 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
- ✦Node.js single-threaded event loop achieves high concurrency through non-blocking IO, unlike Java's multi-threading model
- ✦Async evolution: callbacks → Promise → async/await; modern code should always use async/await
- ✦Use
Promise.allfor parallel execution of independent async operations, instead of sequential await - ✦Microtasks execute before macrotasks — this determines the execution order of Promise callbacks and setTimeout
- ✦
errincatchisunknowntype — must narrow withinstanceofbefore use. The Result pattern is a type-safe alternative to try/catch