竞态条件是多个异步操作争抢更新同一状态导致结果不可控;可用abortcontroller取消过期请求、requestid校验响应时效、broadcastchannel+版本号同步多窗口状态、标志位控制简单串行。

竞态条件不是“多个请求同时发出去”,而是多个异步操作争抢更新同一份状态,结果取决于谁后写入,而非业务逻辑本意。比如用户快速输入搜索词,第2次请求比第1次先返回,却把第1次的正确结果覆盖掉;又或者两个浏览器标签页同时编辑同一草稿,后保存的直接抹掉前者的修改。
用 AbortController 主动取消过期请求
这是最常用、最直接的防御方式,尤其适合搜索建议、刷新按钮连点等场景。关键不在“发”,而在“及时停”:
- 每次发起新 fetch 前,调用上一个 AbortController 的 abort() 方法
- 把当前控制器的 signal 传给 fetch 选项
- 在 catch 中检查 err.name === 'AbortError',遇到就静默忽略,不触发任何 UI 或状态更新
- 每个请求配独立控制器,不跨组件或窗口共享
用 requestId 或时间戳校验响应时效性
有些响应无法取消(比如 Axios 封装的 Promise、WebSocket 消息),那就靠“拒收”来防御:
- 发起请求时生成唯一标识,如 Date.now() 或 Math.random()
- 把这个标识存为当前最新 requestId,并随请求一起发出(可放 header、query 或 body)
- 响应到达后,先比对它携带的 requestId 是否等于当前最新值
- 不相等 → 说明已有新请求发出,直接丢弃该响应,不做任何 setState 或数据合并
多窗口场景:用 BroadcastChannel + 版本号同步状态
单页面里管好自己的请求还不够,多个标签页共用 localStorage 或服务端状态时,竞态会放大:
- 给共享数据(如用户登录态、主题设置、草稿内容)维护一个单调递增的 版本号,存在 localStorage 里
- 任一窗口修改数据时,先升版本、存本地、再通过 BroadcastChannel 广播新版本和变更摘要
- 其他窗口监听到消息后,只在 收到版本 > 本地版本 时才去拉取并更新 UI
- 避免“旧消息覆盖新状态”——这是多窗口竞态最典型的错误模式
简单串行场景:用标志位控制执行节奏
如果只是防止重复提交或自动保存类操作,不需要保留中间结果,标志位足够轻量:
- 声明一个 let isPending = false
- 进入异步函数前加判断:if (isPending) return
- 设置 isPending = true,执行完请求后在 finally 中重置为 false
- 注意:它让操作变成排队执行,不适合需要并发但又不能错乱的复杂交互
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











