Node.js 错误与异常:可恢复边界与错误码

try/catch 能接住同步错误;Promise 里 漏了 await 的拒绝会变成 unhandledRejection
Node 里还有 err.code(如 ENOENT)、AggregateError 这类 结构化信息
这一篇想讲清楚几件事:哪些错误该往上抛、哪些该在边界转成 HTTP 状态码、以及「可恢复」和「进程级」大致怎么划

Error 及其子类通常带:

  • message:给人看的说明;
  • stack:栈追踪(异步栈是否完整受引擎与工具影响);
  • code:不少 Node 系统错误 用字符串码(与 errno 对照可查文档)。

err.code === 'ENOENT' 比字符串匹配 message 稳——后者可能随语言或版本变化。

可以粗分(名字各家不同):

  • 操作错误(operational):预期会发生 的事——文件不存在、网络超时、校验失败;
  • 程序错误(programmer):bug——空指针、错误类型、永远不该发生的断言失败。

操作错误

  • 在边界捕获,转成 日志 + 对用户/调用方的明确结果(HTTP 4xx/5xx、业务错误码);
  • 不要当未处理异常一路冒泡 unless 你真的想让进程挂(有时反而该挂)。

程序错误

  • 测试阶段 修掉;
  • 线上 若仍发生,往往 记录后重启 比「继续跑在坏状态」安全——和 生产运行 篇的退出策略一致。

async 函数里 throw 等价于 拒绝 Promise

常见坑:

  • 顶层 async IIFE 里抛错,若 .catchunhandledRejection
  • Promise.all 里一个失败,要决定 是全部失败 还是 allSettled

习惯:

  • HTTP 中间件集中 try/catch框架的错误钩子,把 reject 转成 响应
  • 后台任务 每个 Promise 末尾接 .catch 记日志,或 void 模式时想清楚

Promise.any、部分 并行 IO 场景会见到 AggregateError

  • errors 数组里 多个子错误

处理:

  • 日志展开子错误,不要只打外层 message
  • 对用户 可能只展示 一条概括 + 内部 trace id

在 Web 服务里可以维护 简单映射

  • 校验失败 → 400;
  • 鉴权 → 401/403;
  • 资源不存在 → 404;
  • 冲突 → 409;
  • 依赖超时 → 502/504(视网关语义)。

关键是 同一类错误全站一致,而不是每个路由手写魔法数字。

  • code / 类型 区分 可预期失败,少靠字符串猜;
  • 操作错误在边界消化程序错误 要有 监控与重启策略
  • Promise 拒绝 必须 有归属,避免 静默 unhandledRejection
  • 多错误结构化聚合,日志要 可检索

错误模型调试观测、HTTP、生产运行 三篇连起来,线上排障会少一半「不知道错从哪来」。