什么是异步数据竞态问题
异步数据竞态问题多发生在多个异步操作并发执行,而后续操作依赖于这些操作的执行顺序或执行结果时,可能导致预期之外的错误或行为
问题表现
- 先后顺序不确定:展示顺序错乱
- 数据更新混乱:比如一个请求先返回,但后来的请求覆盖了它的数据
- UI卡顿或者闪烁:如果多个请求并发执行而没有控制,可能导致页面多次重新渲染
典型问题场景
-
快速切换选项卡
-
搜索框输入联想
这里我用随机数模拟了一下请求的时长,可以看到当我输入“app”的时候结果还算正常,但是当我删除到“a” 的时候,结果展示的是“ap”的搜索结果
避免异步数据竞态问题的方法
异步数据竞态问题通常发生在并发请求中,避免竞态问题的关键在于:合理的请求顺序、并发控制以及对异步结果的正确管理
取消过期请求
实现方式
- 使用
AbortController(现代浏览器原生支持)
let controller;
async function fetchData(query) {
if (controller) controller.abort(); // 取消前一个请求
controller = new AbortController();
try {
const res = await fetch(`/api?q=${query}`, {
signal: controller.signal
});
// 处理数据
} catch (err) {
if (err.name === 'AbortError') return; // 忽略取消错误
}
}
- 第三方库如
axios的cancelToken
适用场景
用户频繁触发异步操作,且只需保留最新结果的场景
典型用例
-
搜索框输入联想
- 用户连续输入时(如输入 "a" → "ap" → "app"),取消之前的请求,只保留最后一次输入的请求结果。
- 避免旧搜索词(如 "a")的响应覆盖新搜索词(如 "app")的结果。
-
选项卡切换
- 用户快速切换选项卡时(如 Tab1 → Tab2 → Tab3),取消之前的 Tab1、Tab2 的请求,确保只展示当前选中 Tab3 的数据。
-
表单防重复提交
- 用户多次点击提交按钮时,取消前一次未完成的请求,确保只提交最后一次操作。(这里其实更推荐做个防抖)
忽略过期响应
这种方法兼容性强,无需依赖特定 API,适用于任何环境,但是过期请求仍在后台运行
实现方式
- 使用requestId控制
let requestId = 0;
async function loadData() {
const currentId = ++requestId;
const data = await fetchData();
if (currentId !== requestId) return; // 忽略过期响应
render(data);
}
- 最近看到一个大佬分享的忽略过期响应的很牛的方法,核心如下
function createCancelTask(asyncTask){
let cancel = () => {}
return (...args) => {
return new Promise((resolve,reject)=>{
cancel()
cancel = () => {
resolve = reject = () => {}
}
asyncTask(...args).then(
(res) => resolve(res),
(err) => reject(err)
)
})
}
}
适用场景
无法取消请求(如兼容性限制或请求已发送到后端)或者需要简单实现,不依赖复杂 API 或第三方库的场景。
典型用例
-
兼容性要求高的环境
- 旧版本浏览器不支持
AbortController时,通过唯一标识(如时间戳、序列号)标记请求,仅处理最新标识对应的响应。
- 旧版本浏览器不支持
-
竞态风险较低的场景
- 如页面初始化时加载多个并行请求,但只需确保最终显示正确数据(例如:仪表盘中的多个统计卡片)。
控制请求顺序
实现方式
这个方法的核心是做一把锁
let isLocked = false;
async function sequentialRequest(data) {
if (isLocked) return;
isLocked = true;
await sendData(data);
isLocked = false;
}
适用场景
需要严格保证异步操作顺序**,避免并发冲突,或者请求依赖前一次请求结果的场景。
典型用例
- 分步表单提交
- 用户分步骤填写表单(如注册流程),每一步提交依赖前一步的结果,需确保请求按顺序执行
- 实时数据同步
- 例如聊天应用,需要保证消息发送的顺序与服务器确认顺序一致,避免消息错乱。
- 防抖与节流
- 合并高频操作(如窗口缩放事件),确保一段时间内只执行最后一次请求。
快去你的项目看看有没有类似的问题吧,或者有什么更好的解决方法欢迎评论区讨论