接口响应顺序与触发顺序不一致,导致后触发的请求被先返回的结果覆盖。
这是典型的请求竞态(Request Race Condition)问题,常见于切换 tab、搜索、下拉选项时短时间内发起多个请求。
✅ 解决方案总结
|
方案 |
原理 |
优点 |
缺点 |
|
1. 请求唯一标识(标记过期) |
发请求时保存当前标识,响应回来后比对是否仍有效 |
简单高效 |
适用于不需要取消请求的场景 |
|
2. |
切换前调用旧请求的 |
能真正取消请求、节省资源 |
仅支持 |
|
3. 手动维护请求状态 |
设置 |
可控性强 |
代码稍繁琐 |
✅ 推荐做法 1:请求唯一标识 + 比对
这是最通用也最简单的方式:
const latestRequestId = useRef(0);
const fetchData = async (tabKey) => {
const requestId = ++latestRequestId.current; // 每次请求递增
const res = await api.getData(tabKey);
if (requestId !== latestRequestId.current) return; // ❌ 已过期请求,忽略
// ✅ 是最后一次发出的请求,才更新数据
setData(res.data);
};
✅ 推荐做法 2:使用 AbortController 取消请求(适用于 fetch)
const controllerRef = useRef(null);
const fetchData = async (tabKey) => {
// 取消之前的请求
if (controllerRef.current) controllerRef.current.abort();
const controller = new AbortController();
controllerRef.current = controller;
try {
const res = await fetch(`/api/data?tab=${tabKey}`, {
signal: controller.signal,
});
const json = await res.json();
setData(json.data);
} catch (e) {
if (e.name === 'AbortError') {
console.log('请求已取消');
}
}
};
如果你使用 axios,可以这样配合 CancelToken:
// 使用 axios 的 cancelToken
const cancelTokenRef = useRef(null);
const fetchData = async (tabKey) => {
if (cancelTokenRef.current) {
cancelTokenRef.current.cancel('取消前一个请求');
}
cancelTokenRef.current = axios.CancelToken.source();
try {
const res = await axios.get('/api/data', {
cancelToken: cancelTokenRef.current.token,
params: { tab: tabKey },
});
setData(res.data);
} catch (err) {
if (axios.isCancel(err)) {
console.log('请求取消');
}
}
};
✅ 推荐做法 3:封装统一处理 hook
你可以封装一个通用的 hook,比如:
function useSafeRequest(requestFn) {
const requestId = useRef(0);
return async (...args) => {
const id = ++requestId.current;
const res = await requestFn(...args);
if (id !== requestId.current) return null;
return res;
};
}
✅ 总结推荐
|
项目类型 |
推荐方案 |
|
React 项目中切换 tab、下拉列表 |
✅ 请求标识 + 比对(方案 1)最简单 |
|
使用 |
✅ |
|
axios 项目 |
✅ |
|
复杂并发控制场景 |
✅ 封装统一处理 hook(方案 3)提高可复用性 |



564

被折叠的 条评论
为什么被折叠?



