async/await 本身不引发竞态,真正原因是共享状态未协调;需加锁、校验请求有效性、不可变更新;并行用 Promise.all;必写 await;关键异步段须 try/catch。

async/await 本身不制造竞态条件,但它让异步逻辑更易读,也更容易掩盖执行顺序问题——真正引发逻辑错误的,是多个异步操作共享状态且未协调执行时机。
明确共享数据的修改边界
当多个 await 操作读写同一变量(如全局缓存、组件 state、数据库记录),若缺乏控制,后发起的请求可能覆盖先完成的结果。例如:用户快速点击两次“刷新”,触发两次 fetchUser,第二次响应先返回并更新 UI,而第一次的数据后到却覆盖了正确结果。
- 对关键状态变更加锁:用一个布尔标记(如 isFetching)阻止重复触发
- 使用唯一请求标识(如 timestamp 或 abortController.signal)在响应到达时校验是否仍有效
- 避免直接赋值共享对象,改用不可变更新或原子操作(如 React 的 setState 函数式更新)
用 Promise.all 替代串行 await 实现可控并发
连续 await 是串行的,但有时你需要的是“同时发起、按需处理”,而非“等一个完再发下一个”。盲目串行不仅慢,还可能因时间差放大竞态风险(比如中间状态被其他逻辑误读)。
- 需要并行获取多个独立资源时,先构造 Promise 数组,再统一 await Promise.all([...])
- 若需部分失败不影响整体,改用 Promise.allSettled 并分别判断每个结果状态
- 避免在循环中无条件 await ——除非业务逻辑明确要求串行(如依赖上一步返回 ID)
确保 await 调用完整且位置合理
漏写 await 是最隐蔽的竞态诱因:看似异步的操作实际以同步方式“发出去就不管”,后续代码继续执行,导致状态错乱或空值访问。
- 所有返回 Promise 的调用都必须 await(包括函数调用、.then 链末端、第三方库方法)
- 注意某些 API 返回的是 Promise 但文档未强调(如 Vue 的 nextTick、某些 SDK 的 save 方法)
- 在 if/else 或循环分支中检查每个异步路径是否都有 await,尤其警惕 early return 场景
用 try/catch 封装关键异步段,防止异常中断流程
未捕获的 Promise rejection 不会阻塞主线程,但会让后续依赖该结果的逻辑跳过执行,造成状态不一致。比如用户信息加载失败后,头像渲染逻辑仍尝试读取 user.name,结果报错或显示 undefined。
- 每个独立业务单元(如一次完整数据加载+渲染)应包裹自己的 try/catch
- catch 中不仅要记录错误,还要提供默认值或重置相关状态,避免残留脏数据
- 避免只在顶层 catch —— 层级太深时,中间某步失败可能导致下游逻辑误判为成功

















