async/await 异常本质是 promise rejection,需用 await 触发转换、try/catch 捕获,避免未 await 导致静默失败;应集中于入口层捕获,保留原始堆栈,构造器禁用 async,fire-and-forget 需异常回调。

async/await 中的异常本质仍是 Promise rejection,关键不是阻止它冒泡,而是确保它在合适的位置被拦截和处理。最核心的原则是:**用 await 触发异常转换,用 try/catch 捕获,避免遗漏未 await 的任务**。
必须 await 才能捕获异常
async 函数返回的是 Promise,如果调用后不 await,异常不会立即抛出,而是留在 Promise 内部——这会导致静默失败或触发未处理 rejection。
- ✅ 正确:await someAsync(); // 异常可被外层 try/catch 捕获
- ❌ 错误:someAsync(); // Promise 被丢弃,异常可能丢失或触发 UnhandledPromiseRejection
- ⚠️ 危险:return someAsync(); // 不 await 直接 return,异常无法被当前函数的 catch 捕获
集中捕获,避免层层 try/catch
异常会自然沿调用链向上冒泡,不需要在每一层都加 try/catch。应在业务入口、API 响应层或用户操作触发点统一处理。
- 比如在控制器方法、按钮点击事件、页面加载逻辑中包裹一层 try/catch
- 中间服务层(如 API 封装、工具函数)通常不主动捕获,除非需做特定转换或日志记录
- 深层调用(fetch → service → utils)中的 throw,只要没被中途拦截,就会直达最外层 try 块
保留原始堆栈信息
手动构造新 Error 会切断调用链,导致调试困难。应直接 throw 原始 error 对象。
- ✅ 正确:throw err; // 保持原始堆栈
- ❌ 错误:throw new Error(err.message); // 新堆栈,丢失上下文
- ? 补充:可在上层添加上下文,如 err.context = "login flow",但不要替换 error 实例
特殊场景:构造器与 fire-and-forget
构造函数不能 async,内部异步操作失败不会自动冒泡;类似地,不 await 的调用(fire-and-forget)也容易丢失异常。
- 构造器里避免 await,改用 init() 方法并显式返回 Promise
- 确实需要 fire-and-forget 时,务必提供异常回调,例如 .SafeFireAndForget(ex => logger.Error(ex))
- 浏览器环境可监听 unhandledrejection 全局事件作兜底,但不应作为主处理逻辑











