但行好事,莫问前程
前言
最近收到运营反馈 —— 订单列表在连续搜索时,经常出现数据失真的情况。
排查了下代码,发现是因为 没有处理竞态请求,这个问题很常见且因为接口延迟的原因,特别容易复现。
竞态请求 - 例如先发送查询请求A,然后切换发送查询请求B,(A,B属于同页面的同一接口,但条件不一样),如果A的响应比B慢,就会导致展示数据与预期内容不符。
本文会介绍如何使用 Abort Controller 解决竞态问题。
如果对你有所帮助,还望点赞、收藏、关注三连😽。
方案
整理一下思路,对这种连续请求导致的竞态场景,有哪些解决方法
- 防抖、节流、loading
- 直接有效的限制请求操作
- 但无法根绝竞态的出现,且loading会存在阻塞用户操作的风险
- canceltoken
axios专用,局限性较大- 官方已推荐使用
AbortController替代它
- AbortController
- JS 原生API,更现代的、标准的、更具通用性
所以更推荐使用 AbortController 优雅地处理 竞态请求 问题,避免数据错乱。
(最好让后端优化下接口😹)
AbortController
AbortController是一个可以让你 主动取消异步操作 的浏览器API,已被主流浏览器(除IE)外所兼容。
它的使用方法如下:
-
new AbortController()创建一个新的
AbortController对象实例。 -
controller.signal返回一个
AbortSignal只读的对象实例,可以用它来和异步操作进行通信或者中止这个操作。 -
AbortController.abort()中止一个尚未完成的异步操作。这能够中止 fetch 请求及任何响应体和流的使用。
// 1. 创建一个控制器实例
const controller = new AbortController();
// 2. 将signal实例与请求绑定
fetch('/example', { signal: controller.signal })
.then((response) => response.json())
.catch(err => {})
// 3. 调用abort取消请求
setTimeout(() => { controller.abort() }, 1000);
实践
项目中,可以将单个控制器绑定在多个请求中(订单数据、交易数据、排名数据...),统一管控。
const abortControllerRef = useRef<AbortController | null>(null);
// 获取仪表板数据
const getDashBoardData = (params: GetDashBoardP) => {
// 取消未完成的请求,避免接口冲突
abortControllerRef.current?.abort();
abortControllerRef.current = new AbortController();
const { signal } = abortControllerRef.current;
setLoading(true);
Promise.allSettled([
getTransactionData(params, signal),
getOrderData(params, signal),
getRankData(params, signal)
])
.catch(() => {})
.finally(() => {
setLoading(false);
});
}
// 触发更新
useEffect(() => {
getDashBoardData(searchParams);
}, [searchParams]);
总结
AbortController 是更现代的、标准的、更具通用性的竞态问题解决方案
适用场景,例如 组件卸载时清理请求、表格搜索、文件上传、清理事件监听
注意 请求还是会到服务端,abortcontroller 只是终止了前端的后续逻辑
在IE场景可以使用canceltoken
结语
不要光看不实践哦,希望本文能对你有所帮助。
持续更新前端知识,脚踏实地不水文,真的不关注一下吗~
写作不易,如果有收获还望 点赞+收藏 🌹
才疏学浅,如有问题或建议还望指教~