catch块非安全区,其内异常会逃逸;应仅做日志、降级、ui提示等轻量操作,避免新请求或dom操作,必要时需嵌套try-catch或移至外层处理。

catch 块本身不是“安全区”,它内部抛出的异常若未被处理,会继续向上冒泡——可能直接变成未捕获错误,甚至覆盖原始异常。这不是边缘情况,而是常见陷阱。
catch 里的错误会逃逸出去
一旦在 catch 块中执行出错(比如调用一个不存在的函数、访问 undefined 属性、或异步操作未兜底),这个新错误不会被当前 try-catch 捕获,也不会自动关联到前一个错误。
- 同步错误:如
console.log(foo.bar)(foo 未定义)→ 报 ReferenceError,跳出 catch,全局抛出 - 异步错误:如
setTimeout(() => { throw new Error('in catch') }, 0)→ 不进任何 catch,触发unhandledrejection或直接报错 - Promise 错误:如
fetch('/fail').then(...)没加.catch()→ 同样变成未处理拒绝
避免在 catch 中引入新风险
catch 的核心任务是“稳住局面”,不是展开新逻辑。优先做轻量、确定、无副作用的操作:
- 记录日志:用
console.error(err)或上报 SDK,确保err.message和err.stack都保留 - 降级返回:如返回默认值、空数组、占位对象,不依赖外部状态或网络
- 触发 UI 提示:调用已封装好的 toast 或 modal 方法(前提是这些方法本身健壮、无异常)
- 避免:发起新请求、解析用户输入、操作 DOM 节点(除非已确认存在)、调用未加防护的第三方函数
需要主动兜底的典型场景
当业务逻辑要求在 catch 中做稍重操作时,必须自己再加一层防护:
- 异步上报错误:
await reportError(err).catch(e => console.warn('错误上报失败', e)) - 重试请求:
try { await fetch(...) } catch (e) { setTimeout(() => safeRetry(), 1000) },其中safeRetry自带 try-catch - 清理资源:
finally更适合;若必须在 catch 中关定时器或取消请求,先判空再调用:controller?.abort()
更稳妥的替代思路
与其在 catch 里“硬扛”,不如把高风险动作移到更外层或独立流程:
- 将错误处理逻辑拆成纯函数,并单独单元测试其健壮性
- 用
unhandledrejection监听兜底所有漏掉的 Promise 错误,作为最后一道防线 - 对关键路径(如登录、支付)设计“错误状态机”,把恢复动作与原始流程解耦,避免嵌套失控











