回调与错误栈
callbacks、callstack、异步错误。
25 分钟
回调与错误栈
nodejs-essentials 已学 Promise 与 async/await,但 Node 底层与许多老库仍用 callback(err, result) 约定。本课讲 call stack、异步错误传播、stack trace 丢失——写可靠回调和迁移到 Promise 的基础。
学习目标
- 理解 Node 错误优先回调约定
(err, data) => {} - 区分同步 throw 与异步 callback(err) 的行为差异
- 用
util.promisify、async_hooks改善错误栈可读性
错误优先回调
Node 核心 API(fs 老接口、部分 stream)约定:第一个参数是 Error 或 null。
import fs from "node:fs";
fs.readFile("missing.txt", "utf8", (err, data) => {
if (err) {
console.error("read failed:", err.message);
return;
}
console.log(data.length);
});
规则:成功时 err === null;失败时 err 为 Error 实例,不要再读 data。
Call Stack 与异步断档
同步代码 throw 时,stack 从 throw 点到 catch 连续。异步 callback 里 throw 若没有域或 try/catch,会变成 uncaughtException 并可能崩溃进程:
setTimeout(() => {
throw new Error("async boom"); // 不在外层 try/catch 范围内
}, 0);
try { setTimeout(...) } catch 捕不到 定时器内的 throw——回调在另一轮 Event Loop 执行。
最佳实践(衔接 nva-errors):
- callback 风格:
if (err) return cb(err) - Promise:
main().catch(err => { ...; process.exitCode = 1 }) - EventEmitter:监听
error事件,否则 unhandled 会 crash
回调地狱与 promisify
import { promisify } from "node:util";
import fs from "node:fs";
const readFile = promisify(fs.readFile);
async function loadConfig(path) {
const text = await readFile(path, "utf8");
return JSON.parse(text);
}
promisify 要求 callback 遵循 (err, result);多结果 API 需自定义 wrapper。
异步错误栈:cause 与 async_hooks
Node 16+ Error 支持 cause 链:
try {
await readFile("x.txt");
} catch (err) {
throw new Error("load config failed", { cause: err });
}