async/await本身不提供异常注入能力,它只是消费Promise的语法糖;真正的异常注入需在await前主动throw、封装异步调用重抛增强错误、或在Promise中reject自定义Error实例,并避免未await、静默吞错、上下文污染等陷阱。
async/await 本身不提供“异常注入”能力——它只是语法糖,用于更清晰地消费 Promise。所谓“异常注入”,实际是指在异步流程中主动触发、构造并传递错误,使其能被上层 try/catch 捕获,同时携带上下文信息(如 traceId、操作名、参数等),而非简单抛出原始错误。
在 await 前主动 throw 错误
最直接的方式是在 await 表达式执行前,根据业务条件提前抛出错误,让控制流进入 catch 分支:
- 适用于参数校验、权限检查、前置状态判断等场景
- 错误会立即被外层 try/catch 捕获,stack trace 清晰指向当前函数位置
- 示例:if (!userId) throw new Error('Missing userId');
封装异步调用并重抛增强型错误
避免裸写 await fetch() 或 await db.query(),统一用工具函数包装,在 catch 中构造带上下文的错误对象:
- 保留原始 error.stack,并附加当前层信息(如 URL、SQL、traceId)
- 推荐使用自定义错误类(如
AppError),含 code、context、cause 字段 - 示例:throw new AppError('DB_TIMEOUT', { sql, traceId }, originalErr);
在 Promise 构造器中 reject 自定义错误
当手动创建 Promise(如封装回调 API、定时器、事件监听)时,可在 reject 时传入结构化错误:
- 确保 Promise 被 await 后,该 reject 能被 try/catch 正确捕获
- 不要忽略 reject 的值类型——建议传 Error 实例,而非字符串
- 示例:reject(new AppError('TIMEOUT', { timeout: 5000 }));
避免常见陷阱
以下做法会导致异常“消失”或无法溯源:
- 调用 async 函数但未 await,也未接
.catch() → 异常变成 unhandled rejection - 在 catch 中只 console.error 却不 re-throw 或返回 fallback → 错误被静默吞掉
- 用全局变量存 traceId 或 error 上下文 → 多请求并发时发生污染
- 拼接 stack 时丢弃原始 error.cause 或未保留 new Error().stack → 调用链断裂