JavaScript 异步与竞态问题
⏱️ 用于记录请求乱序、重复提交、过期闭包、定时器与组件销毁后回调等异步问题。
常见症状
- 后发请求先返回,旧结果覆盖新结果
- 快速点击后出现重复提交或重复弹窗
- 页面切换后仍更新已销毁组件
- 定时器、订阅或事件监听未清理
await前后读取到的状态不一致
典型根因
- 缺少请求身份或版本校验
- 多个异步任务共享可变状态
- 没有取消过期任务
- 防抖、节流或锁的边界设计错误
- 闭包捕获了旧状态
排查方法
- 为每次任务记录
requestId、开始时间和结束时间 - 使用 Network 面板确认请求与响应顺序
- 在关键状态写入点打印调用栈和当前版本
- 人为增加延迟,连续触发操作以稳定复现
- 检查卸载流程中的
abort、clearTimeout和取消订阅
修复模式
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 次结果仍正确
- 慢网、失败和超时场景已覆盖
- 页面离开后不再写入状态
- 重复提交具备幂等或前端锁保护
- 相关定时器与订阅均能释放