Error.cause在Node.js 16–18.16中无法自动传递,因原生Error构造函数不自动挂载cause属性,须手动赋值;18.17+/20.7+才支持new Error(msg, {cause})语法并使error.cause可读且序列化保留。

为什么 Error.cause 在 Node.js 16+ 中无法自动传递异常链?
Node.js 原生 Error 构造函数不会把传入的 cause 自动挂载到实例属性上,除非你显式调用 super({ cause }) 或使用 new Error(message, { cause })(仅限 Node.js 18.17+ / 20.7+)。微服务中常见做法是捕获上游错误后包装成新错误并设 cause,但若没正确初始化,下游拿到的只是空 error.cause,链路断在第一跳。
- Node.js 16–18.16:必须手动赋值
error.cause = originalError,否则不生效 - Node.js 18.17+ / 20.7+:支持
new Error('timeout', { cause: upstreamErr }),此时error.cause可读且序列化时保留 - 注意:JSON.stringify(error) 默认忽略
cause,需自定义toJSON或用专用序列化函数
如何在 Express 中间件里正确包装并透传 cause 链?
Express 默认错误处理中间件(app.use((err, req, res, next) => {...}))接收到的 err 往往已丢失原始 cause,尤其当上游用 throw new Error(...) 直接抛出时。关键是在每个服务出口处主动构造带因果的错误。
- 不要写
throw new Error('DB failed'),而要写throw new Error('DB failed', { cause: dbError }) - 若 dbError 是第三方库错误(如 pg 的
DatabaseError),它本身可能没有cause,但你可以补全:const wrapped = new Error('Query timeout', { cause: dbError }); wrapped.code = 'TIMEOUT'; - 在中间件中统一提取链路 ID 并注入错误属性:
err.traceId = req.headers['x-trace-id'],避免只依赖cause查源
跨服务 HTTP 调用时,cause 链如何通过响应体还原?
HTTP 协议本身不传输 JS 错误对象,cause 必须序列化后随响应体返回,并在调用方反序列化重建。不能依赖 JSON.stringify(err) —— 它丢掉 cause、stack 和非枚举属性。
- 服务端响应应包含结构化错误体:
{"message":"Service B unavailable","code":"UNAVAILABLE","cause":{"message":"Connection refused","code":"ECONNREFUSED"}} - 客户端用专用函数解析:
function fromHttpError(body) { return new Error(body.message, { cause: body.cause ? new Error(body.cause.message, { code: body.cause.code }) : undefined }); } - 务必校验
body.cause存在且字段完整,缺失时 fallback 为null而非undefined,避免TypeError: Cannot read property 'message' of undefined
日志和 APM 工具怎么真正利用 cause 链做根因分析?
多数日志库(如 pino、winston)默认不展开 error.cause,APM 工具(如 Datadog、New Relic)也需显式配置才能采集嵌套错误。不处理就等于白加。
- pino 示例:启用
serializers: { err: pino.stdSerializers.err }不够,需自定义err序列化器,递归处理cause - Datadog SDK 要求手动调用
span.addTags({ 'error.cause.message': err.cause?.message }),深层嵌套需循环遍历 - 最易被忽略的一点:
error.stack里的行号指向包装错误的位置,不是原始错误;调试时得结合cause.stack才能准确定位

















