大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
回调与错误栈
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 });
}
调试深层 async 链时,开启 --async-stack-traces(V8 默认在许多版本已改善)或在测试里断言 err.cause。
生产环境避免把完整 stack 返回给客户端;日志里保留 stack(见 nrt-apm)。
与 Java 异常对照
| Node callback / Promise | Java | |
|---|---|---|
| 错误类型 | 多为 Error 对象,非类型化 | checked / unchecked |
| 异步 | 必须显式传 err 或 reject | CompletableFuture |
| 未捕获 | uncaughtException / unhandledRejection | Thread uncaught handler |
练习
- 写 callback 版
readJson(path, cb),错误时传SyntaxError或ENOENT - 同一逻辑用
promisify+ async/await 重写 - 故意在 setTimeout 内 throw,观察进程行为与
--unhandled-rejections=strict差异
小结
回调要 err-first;异步 throw 不会被外层同步 catch 接住。 下一课子进程——把任务挪到独立进程隔离崩溃与 CPU。