跳到主要内容

JavaScript 异步与竞态问题

⏱️ 用于记录请求乱序、重复提交、过期闭包、定时器与组件销毁后回调等异步问题。

常见症状

  • 后发请求先返回,旧结果覆盖新结果
  • 快速点击后出现重复提交或重复弹窗
  • 页面切换后仍更新已销毁组件
  • 定时器、订阅或事件监听未清理
  • await 前后读取到的状态不一致

典型根因

  1. 缺少请求身份或版本校验
  2. 多个异步任务共享可变状态
  3. 没有取消过期任务
  4. 防抖、节流或锁的边界设计错误
  5. 闭包捕获了旧状态

排查方法

  • 为每次任务记录 requestId、开始时间和结束时间
  • 使用 Network 面板确认请求与响应顺序
  • 在关键状态写入点打印调用栈和当前版本
  • 人为增加延迟,连续触发操作以稳定复现
  • 检查卸载流程中的 abortclearTimeout 和取消订阅

修复模式

AbortController 取消旧请求

let controller: AbortController | undefined

async function search(keyword: string) {
controller?.abort()
controller = new AbortController()
const response = await fetch(`/api/search?q=${encodeURIComponent(keyword)}`, {
signal: controller.signal,
})
return response.json()
}

版本号阻止旧结果覆盖

let version = 0

async function load() {
const current = ++version
const data = await fetchData()
if (current !== version) return
render(data)
}

回归验证

  • 连续快速触发 10 次结果仍正确
  • 慢网、失败和超时场景已覆盖
  • 页面离开后不再写入状态
  • 重复提交具备幂等或前端锁保护
  • 相关定时器与订阅均能释放