try-catch仅捕获竞态引发的错误,不防止竞态;防竞态需协调共享状态访问,如用abortcontroller过滤过期响应、分层try-catch隔离失败、promise.allsettled处理并发、finally只做同步清理。

在 async 函数中用 try-catch 本身不解决并发竞争,它只是错误捕获工具;真正防止竞态的关键是协调共享状态的访问时机。try-catch 的作用,是在竞态发生后(比如数据被覆盖、校验失败、请求过期)及时捕获异常并做兜底处理,而不是阻止竞态产生。
明确哪些操作会引发竞态
常见诱因包括:
- 多次快速触发同一异步操作(如双击提交、频繁刷新),导致多个 fetch 同时进行,后返回的数据覆盖先返回的结果
- 多个 await 操作读写同一个变量(如全局缓存、React state、Vuex store),且未加有效性校验
- 串行依赖链中,中间步骤结果被后续异步逻辑误读(例如:await getUser() → 立即更新 UI → 但此时 await getPosts() 还没完成,其他代码却已基于不完整数据执行)
用信号机制过滤过期响应
给每次请求绑定唯一标识,在响应到达时判断是否仍有效:
- 用 AbortController.signal 配合 fetch,请求取消后,其响应不再被接受
- 手动维护一个 requestId 或时间戳,在发起请求时记录,在 then/catch 中比对当前活跃 ID 是否匹配
- 示例:用户点击刷新,先 abort 当前 controller,再新建一个;收到响应时检查 signal.aborted === false 再更新状态
按业务单元分层 try-catch,避免连锁中断
不要把一连串异步调用全塞进一个 try 块。应按语义拆分,让失败局部化:
- 获取用户信息失败 → 返回默认对象,不影响后续帖子加载
- 加载帖子失败 → 显示空列表,但头像和昵称照常渲染
- 每个关键 await 单独加 .catch() 或包裹小 try-catch,而不是共用一个大块
- 这样即使某一步出错,其余流程仍可推进,减少“雪崩式失败”
并发控制优先用 Promise.allSettled 而非 all
当需要同时发起多个独立请求时:
- Promise.all:任一失败即整体 reject,无法拿到其余成功结果
- Promise.allSettled:始终 resolve,返回每个 Promise 的 status + value 或 reason,便于分别判断和恢复
- 配合 try-catch 可在每个 settled 结果里做细粒度处理:成功则更新对应字段,失败则打日志或设 loading 为 false
finally 中只做同步清理,不 await 异步操作
finally 块保证执行,但若在里面 await 异步操作,会导致函数实际返回延迟,破坏调用方对时序的预期:
- ✅ 正确:关闭 loading、重置 isSubmitting、清空临时缓存(都是同步赋值)
- ❌ 错误:await api.reportError()、await localStorage.setItem() —— 这些可能失败或卡住,且错误无法被外层 catch 捕获
- 如需异步清理,应在 finally 外单独发起,并用 .catch(() => {}) 静默处理











