Node.js 错误与异常:可恢复边界与错误码
try/catch能接住同步错误;Promise 里 漏了await的拒绝会变成unhandledRejection。
Node 里还有err.code(如ENOENT)、AggregateError这类 结构化信息。
这一篇想讲清楚几件事:哪些错误该往上抛、哪些该在边界转成 HTTP 状态码、以及「可恢复」和「进程级」大致怎么划。
Error 对象:message、stack 与 code
Error 及其子类通常带:
message:给人看的说明;stack:栈追踪(异步栈是否完整受引擎与工具影响);code:不少 Node 系统错误 用字符串码(与errno对照可查文档)。
err.code === 'ENOENT' 比字符串匹配 message 稳——后者可能随语言或版本变化。
操作类 vs 程序类:心智上的两条线
可以粗分(名字各家不同):
- 操作错误(operational):预期会发生 的事——文件不存在、网络超时、校验失败;
- 程序错误(programmer):bug——空指针、错误类型、永远不该发生的断言失败。
对 操作错误:
- 在边界捕获,转成 日志 + 对用户/调用方的明确结果(HTTP 4xx/5xx、业务错误码);
- 不要当未处理异常一路冒泡 unless 你真的想让进程挂(有时反而该挂)。
对 程序错误:
- 测试阶段 修掉;
- 线上 若仍发生,往往 记录后重启 比「继续跑在坏状态」安全——和 生产运行 篇的退出策略一致。
Promise 与 async:拒绝要「有主」
async 函数里 throw 等价于 拒绝 Promise。
常见坑:
- 顶层 async IIFE 里抛错,若 没
.catch→unhandledRejection; Promise.all里一个失败,要决定 是全部失败 还是allSettled。
习惯:
- HTTP 中间件 里 集中
try/catch或 框架的错误钩子,把 reject 转成 响应; - 后台任务 每个 Promise 末尾接
.catch记日志,或 用void模式时想清楚。
AggregateError:一次失败里包多个原因
Promise.any、部分 并行 IO 场景会见到 AggregateError:
errors数组里 多个子错误。
处理:
- 日志 里 展开子错误,不要只打外层
message; - 对用户 可能只展示 一条概括 + 内部 trace id。
与框架:错误到状态码的映射表
在 Web 服务里可以维护 简单映射:
- 校验失败 → 400;
- 鉴权 → 401/403;
- 资源不存在 → 404;
- 冲突 → 409;
- 依赖超时 → 502/504(视网关语义)。
关键是 同一类错误全站一致,而不是每个路由手写魔法数字。
小结:错误是契约的一部分
- 用
code/ 类型 区分 可预期失败,少靠字符串猜; - 操作错误在边界消化,程序错误 要有 监控与重启策略;
- Promise 拒绝 必须 有归属,避免 静默
unhandledRejection; - 多错误 用 结构化聚合,日志要 可检索。
把 错误模型 和 调试观测、HTTP、生产运行 三篇连起来,线上排障会少一半「不知道错从哪来」。