用 AbortController 取消 fetch 请求,别再让旧数据覆盖新结果
在搜索框输入关键词时,每敲一个字符就发一次请求,往往前一个请求比后一个慢,最后渲染出来的却是旧关键词的结果。这个场景在管理后台的表格筛选、地图拖拽加载点位上反复出现。问题根源是 fetch 本身没有取消能力,请求一旦发出就会完整走完,而回调里又直接 setState,谁最后返回谁就覆盖。早期常用一个 flag 变量或 axios 的 CancelToken 来挡住旧响应,但前者只是忽略结果、请求仍在跑,后者已经停止维护。浏览器原生提供了 AbortController,配合 fetch 的 signal 参数可以真正中断连接,同时也能在组件卸载时清理副作用。这篇文章从实际搜索框的坑出发,整理 AbortController 的用法、与 React/Vue 生命周期的配合、超时组合以及几个容易踩的细节。 ## 一个会出问题的搜索组件 先看一段 React 里常见的写法: ```jsx function SearchBox() { const [keyword, setKeyword] = useState(''); const [list, setList] = useState([]); useEffect(() => { if (!keyword) return; fetch(`/api/search?q=${keyword}`) .then(res => res.json()) .then(data => setList(data)); }, [keyword]); return ( setKeyword(e.target.value)} /> ); } ``` 输入 “vue” 时可能依次触发 v、vu、vue 三个请求。如果 vu 的响应比 vue 晚到,列表最终显示的是 “vu” 的结果,用户看到的和输入框里的词对不上。另外组件卸载后 setList 也会触发警告。 ## AbortController 的基本用法 AbortController 是一个构造函数,实例上有 signal 属性和 abort 方法。把 signal 传给 fetch 的 options,调用 abort() 后 fetch 会以 AbortError 拒绝: ```js const controller = new AbortController(); fetch('/api/search?q=vue', { signal: controller.signal }) .then(res => res.json()) .then(data => console.log(data)) .catch(err => { if (err.name === 'AbortError') { console.log('请求已被取消'); return; } throw err; }); // 需要时取消 controller.abort(); ``` 判断取消不能只看 err.message,不同浏览器文案不一致,用 err.name === 'AbortError' 更稳妥。注意 abort 之后再传同一个 signal 给新的 fetch,新的请求会立刻被拒绝,因为 signal 一旦 aborted 就是终止状态,不能复用。每次请求要新建 controller。 ## 接入 React 的 useEffect 把 controller 放到 effect 内部创建,并在 cleanup 里 abort,就能让上一次请求在依赖变化时失效: ```jsx useEffect(() => { if (!keyword) { setList([]); return; } const controller = new AbortController(); fetch(`/api/search?q=${encodeURIComponent(keyword)}`, { signal: controller.signal }) .then(res => res.json()) .then(data => setList(data)) .catch(err => { if (err.name !== 'AbortError') { console.error(err); } }); return () => controller.abort(); }, [keyword]); ``` keyword 变化时,React 先执行上一次 effect 的 cleanup,中断旧请求,再跑新 effect。组件卸载时同样会 abort,省掉了 setState on unmounted component 的警告。 如果不想在 catch 里到处判断,可以封装一个辅助函数: ```js async function request(url, options = {}) { const res = await fetch(url, options); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } // 调用 useEffect(() => { const controller = new AbortController(); request(url, { signal: controller.signal }) .then(setList) .catch(err => { if (err.name !== 'AbortError') setError(err); }); return () => controller.abort(); }, [url]); ``` ## Vue 3 组合式函数里的处理 Vue 3 里同样在 watch 或 onMounted 中创建 controller,并在 onCleanup 或 onUnmounted 里取消: ```js import { ref, watch } from 'vue'; export function useSearch() { const keyword = ref(''); const list = ref([]); let controller; watch(keyword, (val, _, onCleanup) => { if (controller) controller.abort(); if (!val) { list.value = []; return; } controller = new AbortController(); onCleanup(() => controller.abort()); fetch(`/api/search?q=${encodeURIComponent(val)}`, { signal: controller.signal }) .then(res => res.json()) .then(data => { list.value = data; }) .catch(err => { if (err.name !== 'AbortError') console.error(err); }); }); return { keyword, list }; } ``` watch 的 onCleanup 回调在下次执行前和组件卸载时触发,语义和 React 的 effect cleanup 对应。 ## 超时控制与错误区分 fetch 没有内置 timeout,过去靠 Promise.race 加一个 setTimeout,现在可以用 AbortController 统一处理,同时保留业务取消和超时取消两种来源: ```js function fetchWithTimeout(url, options = {}, timeout = 8000) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeout); // 允许外部传入 signal,比如组件卸载时取消 const externalSignal = options.signal; const onExternalAbort = () => controller.abort(); externalSignal?.addEventListener('abort', onExternalAbort); return fetch(url, { ...options, signal: controller.signal }) .finally(() => { clearTimeout(timer); externalSignal?.removeEventListener('abort', onExternalAbort); }); } ``` 这样超时和组件卸载都会触发 abort,catch 里拿到的都是 AbortError,如果要区分,可以在 abort 前设置一个标志,或者用 controller.abort(reason) 传入原因(现代浏览器支持 abort(reason),信号的 reason 会挂到 error 上)。 ## 几个实际会遇到的细节 abort 之后 fetch promise 会 reject,所以一定要有 catch,否则控制台会冒出 unhandled rejection。如果中间经过了响应拦截器或者自己封装的 axios,要确认取消链路有没有透传 signal,很多团队混用 axios 和 fetch,取消方式并不互通。 还有一点,AbortController 的 signal 也可以传给 addEventListener,用来移除事件监听,跟请求取消是同一个机制。这点在写自定义 hook 或工具函数时偶尔会用到。 上游服务如果对每次搜索都做了计费或限流,取消请求能省掉真实开销,因为浏览器会关闭连接,服务端也能通过 request 的 abort 事件感知。不过要注意,取消只是停止等待和接收,服务端已经执行的部分不一定能回滚。 从工程角度看,把取消逻辑收敛到统一的请求层,比在每个组件里手写 controller 更可控。尤其是有了 React Query、SWR、VueUse 的 useFetch 之后,它们内部已经处理了取消,但理解 AbortController 本身,排查竞态问题时心里有底。 推荐查看天气:https://www.0dtq.com/babu/40tian.html 推荐查看天气:https://www.0dtq.com/zhaoping/ 推荐查看天气:https://www.0dtq.com/zhaoping/zuotian.html 推荐查看天气:https://www.0dtq.com/zhaoping/7tian.html 推荐查看天气:https://www.0dtq.com/zhaoping/10tian.html 推荐查看天气:https://www.0dtq.com/zhaoping/15tian.html 推荐查看天气:https://www.0dtq.com/zhaoping/40tian.html 推荐查看天气:https://www.0dtq.com/hezhouzhongshan/ 推荐查看天气:https://www.0dtq.com/hezhouzhongshan/zuotian.html 推荐查看天气:https://www.0dtq.com/hezhouzhongshan/7tian.html 推荐查看天气:https://www.0dtq.com/hezhouzhongshan/10tian.html 推荐查看天气:https://www.0dtq.com/hezhouzhongshan/15tian.html 推荐查看天气:https://www.0dtq.com/hezhouzhongshan/40tian.html 推荐查看天气:https://www.0dtq.com/fuchuan/ 推荐查看天气:https://www.0dtq.com/fuchuan/zuotian.html 推荐查看天气:https://www.0dtq.com/fuchuan/7tian.html 推荐查看天气:https://www.0dtq.com/fuchuan/10tian.html 推荐查看天气:https://www.0dtq.com/fuchuan/15tian.html 推荐查看天气:https://www.0dtq.com/fuchuan/40tian.html 推荐查看天气:https://www.0dtq.com/hechi/ 推荐查看天气:https://www.0dtq.com/hechi/zuotian.html 推荐查看天气:https://www.0dtq.com/hechi/7tian.html 推荐查看天气:https://www.0dtq.com/hechi/10tian.html 推荐查看天气:https://www.0dtq.com/hechi/15tian.html 推荐查看天气:https://www.0dtq.com/hechi/40tian.html 推荐查看天气:https://www.0dtq.com/jinchengjiang/ 推荐查看天气:https://www.0dtq.com/jinchengjiang/zuotian.html 推荐查看天气:https://www.0dtq.com/jinchengjiang/7tian.html 推荐查看天气:https://www.0dtq.com/jinchengjiang/10tian.html 推荐查看天气:https://www.0dtq.com/jinchengjiang/15tian.html 推荐查看天气:https://www.0dtq.com/jinchengjiang/40tian.html 推荐查看天气:https://www.0dtq.com/nandan/ 推荐查看天气:https://www.0dtq.com/nandan/zuotian.html 推荐查看天气:https://www.0dtq.com/nandan/7tian.html 推荐查看天气:https://www.0dtq.com/nandan/10tian.html 推荐查看天气:https://www.0dtq.com/nandan/15tian.html 推荐查看天气:https://www.0dtq.com/nandan/40tian.html 推荐查看天气:https://www.0dtq.com/tiane/ 推荐查看天气:https://www.0dtq.com/tiane/zuotian.html 推荐查看天气:https://www.0dtq.com/tiane/7tian.html 推荐查看天气:https://www.0dtq.com/tiane/10tian.html 推荐查看天气:https://www.0dtq.com/tiane/15tian.html 推荐查看天气:https://www.0dtq.com/tiane/40tian.html 推荐查看天气:https://www.0dtq.com/fengshan/ 推荐查看天气:https://www.0dtq.com/fengshan/zuotian.html 推荐查看天气:https://www.0dtq.com/fengshan/7tian.html 推荐查看天气:https://www.0dtq.com/fengshan/10tian.html 推荐查看天气:https://www.0dtq.com/fengshan/15tian.html 推荐查看天气:https://www.0dtq.com/fengshan/40tian.html 推荐查看天气:https://www.0dtq.com/donglan/ 推荐查看天气:https://www.0dtq.com/donglan/zuotian.html 推荐查看天气:https://www.0dtq.com/donglan/7tian.html 推荐查看天气:https://www.0dtq.com/donglan/10tian.html 推荐查看天气:https://www.0dtq.com/donglan/15tian.html 推荐查看天气:https://www.0dtq.com/donglan/40tian.html 推荐查看天气:https://www.0dtq.com/luocheng/ 推荐查看天气:https://www.0dtq.com/luocheng/zuotian.html 推荐查看天气:https://www.0dtq.com/luocheng/7tian.html 推荐查看天气:https://www.0dtq.com/luocheng/10tian.html 推荐查看天气:https://www.0dtq.com/luocheng/15tian.html 推荐查看天气:https://www.0dtq.com/luocheng/40tian.html 推荐查看天气:https://www.0dtq.com/huanjiang/ 推荐查看天气:https://www.0dtq.com/huanjiang/zuotian.html 推荐查看天气:https://www.0dtq.com/huanjiang/7tian.html 推荐查看天气:https://www.0dtq.com/huanjiang/10tian.html 推荐查看天气:https://www.0dtq.com/huanjiang/15tian.html 推荐查看天气:https://www.0dtq.com/huanjiang/40tian.html 推荐查看天气:https://www.0dtq.com/bama/ 推荐查看天气:https://www.0dtq.com/bama/zuotian.html 推荐查看天气:https://www.0dtq.com/bama/7tian.html 推荐查看天气:https://www.0dtq.com/bama/10tian.html 推荐查看天气:https://www.0dtq.com/bama/15tian.html 推荐查看天气:https://www.0dtq.com/bama/40tian.html 推荐查看天气:https://www.0dtq.com/duan/ 推荐查看天气:https://www.0dtq.com/duan/zuotian.html 推荐查看天气:https://www.0dtq.com/duan/7tian.html 推荐查看天气:https://www.0dtq.com/duan/10tian.html 推荐查看天气:https://www.0dtq.com/duan/15tian.html 推荐查看天气:https://www.0dtq.com/duan/40tian.html 推荐查看天气:https://www.0dtq.com/dahua/ 推荐查看天气:https://www.0dtq.com/dahua/zuotian.html 推荐查看天气:https://www.0dtq.com/dahua/7tian.html 推荐查看天气:https://www.0dtq.com/dahua/10tian.html 推荐查看天气:https://www.0dtq.com/dahua/15tian.html 推荐查看天气:https://www.0dtq.com/dahua/40tian.html 推荐查看天气:https://www.0dtq.com/yizhou/ 推荐查看天气:https://www.0dtq.com/yizhou/zuotian.html 推荐查看天气:https://www.0dtq.com/yizhou/7tian.html 推荐查看天气:https://www.0dtq.com/yizhou/10tian.html 推荐查看天气:https://www.0dtq.com/yizhou/15tian.html 推荐查看天气:https://www.0dtq.com/yizhou/40tian.html 推荐查看天气:https://www.0dtq.com/laibin/ 推荐查看天气:https://www.0dtq.com/laibin/zuotian.html 推荐查看天气:https://www.0dtq.com/laibin/7tian.html 推荐查看天气:https://www.0dtq.com/laibin/10tian.html 推荐查看天气:https://www.0dtq.com/laibin/15tian.html 推荐查看天气:https://www.0dtq.com/laibin/40tian.html 推荐查看天气:https://www.0dtq.com/xingbin/ 推荐查看天气:https://www.0dtq.com/xingbin/zuotian.html 推荐查看天气:https://www.0dtq.com/xingbin/7tian.html 推荐查看天气:https://www.0dtq.com/xingbin/10tian.html 推荐查看天气:https://www.0dtq.com/xingbin/15tian.html 推荐查看天气:https://www.0dtq.com/xingbin/40tian.html 推荐查看天气:https://www.0dtq.com/laibinxincheng/ 推荐查看天气:https://www.0dtq.com/laibinxincheng/zuotian.html 推荐查看天气:https://www.0dtq.com/laibinxincheng/7tian.html 推荐查看天气:https://www.0dtq.com/laibinxincheng/10tian.html 推荐查看天气:https://www.0dtq.com/laibinxincheng/15tian.html 推荐查看天气:https://www.0dtq.com/laibinxincheng/40tian.html 推荐查看天气:https://www.0dtq.com/xiangzhou/ 推荐查看天气:https://www.0dtq.com/xiangzhou/zuotian.html 推荐查看天气:https://www.0dtq.com/xiangzhou/7tian.html 推荐查看天气:https://www.0dtq.com/xiangzhou/10tian.html 推荐查看天气:https://www.0dtq.com/xiangzhou/15tian.html 推荐查看天气:https://www.0dtq.com/xiangzhou/40tian.html 推荐查看天气:https://www.0dtq.com/wuxuan/ 推荐查看天气:https://www.0dtq.com/wuxuan/zuotian.html 推荐查看天气:https://www.0dtq.com/wuxuan/7tian.html 推荐查看天气:https://www.0dtq.com/wuxuan/10tian.html 推荐查看天气:https://www.0dtq.com/wuxuan/15tian.html 推荐查看天气:https://www.0dtq.com/wuxuan/40tian.html 推荐查看天气:https://www.0dtq.com/jinxiu/ 推荐查看天气:https://www.0dtq.com/jinxiu/zuotian.html 推荐查看天气:https://www.0dtq.com/jinxiu/7tian.html 推荐查看天气:https://www.0dtq.com/jinxiu/10tian.html 推荐查看天气:https://www.0dtq.com/jinxiu/15tian.html 推荐查看天气:https://www.0dtq.com/jinxiu/40tian.html 推荐查看天气:https://www.0dtq.com/heshan/ 推荐查看天气:https://www.0dtq.com/heshan/zuotian.html 推荐查看天气:https://www.0dtq.com/heshan/7tian.html 推荐查看天气:https://www.0dtq.com/heshan/10tian.html 推荐查看天气:https://www.0dtq.com/heshan/15tian.html 推荐查看天气:https://www.0dtq.com/heshan/40tian.html 推荐查看天气:https://www.0dtq.com/chongzuo/ 推荐查看天气:https://www.0dtq.com/chongzuo/zuotian.html 推荐查看天气:https://www.0dtq.com/chongzuo/7tian.html 推荐查看天气:https://www.0dtq.com/chongzuo/10tian.html 推荐查看天气:https://www.0dtq.com/chongzuo/15tian.html 推荐查看天气:https://www.0dtq.com/chongzuo/40tian.html 推荐查看天气:https://www.0dtq.com/jiangzhou/ 推荐查看天气:https://www.0dtq.com/jiangzhou/zuotian.html 推荐查看天气:https://www.0dtq.com/jiangzhou/7tian.html 推荐查看天气:https://www.0dtq.com/jiangzhou/10tian.html 推荐查看天气:https://www.0dtq.com/jiangzhou/15tian.html 推荐查看天气:https://www.0dtq.com/jiangzhou/40tian.html 推荐查看天气:https://www.0dtq.com/fusui/ 推荐查看天气:https://www.0dtq.com/fusui/zuotian.html 推荐查看天气:https://www.0dtq.com/fusui/7tian.html 推荐查看天气:https://www.0dtq.com/fusui/10tian.html 推荐查看天气:https://www.0dtq.com/fusui/15tian.html 推荐查看天气:https://www.0dtq.com/fusui/40tian.html 推荐查看天气:https://www.0dtq.com/ningming/ 推荐查看天气:https://www.0dtq.com/ningming/zuotian.html 推荐查看天气:https://www.0dtq.com/ningming/7tian.html 推荐查看天气:https://www.0dtq.com/ningming/10tian.html 推荐查看天气:https://www.0dtq.com/ningming/15tian.html 推荐查看天气:https://www.0dtq.com/ningming/40tian.html 推荐查看天气:https://www.0dtq.com/longzhou/ 推荐查看天气:https://www.0dtq.com/longzhou/zuotian.html 推荐查看天气:https://www.0dtq.com/longzhou/7tian.html 推荐查看天气:https://www.0dtq.com/longzhou/10tian.html 推荐查看天气:https://www.0dtq.com/longzhou/15tian.html 推荐查看天气:https://www.0dtq.com/longzhou/40tian.html 推荐查看天气:https://www.0dtq.com/daxin/ 推荐查看天气:https://www.0dtq.com/daxin/zuotian.html 推荐查看天气:https://www.0dtq.com/daxin/7tian.html 推荐查看天气:https://www.0dtq.com/daxin/10tian.html 推荐查看天气:https://www.0dtq.com/daxin/15tian.html 推荐查看天气:https://www.0dtq.com/daxin/40tian.html 推荐查看天气:https://www.0dtq.com/tiandeng/ 推荐查看天气:https://www.0dtq.com/tiandeng/zuotian.html 推荐查看天气:https://www.0dtq.com/tiandeng/7tian.html 推荐查看天气:https://www.0dtq.com/tiandeng/10tian.html 推荐查看天气:https://www.0dtq.com/tiandeng/15tian.html 推荐查看天气:https://www.0dtq.com/tiandeng/40tian.html 推荐查看天气:https://www.0dtq.com/pingxiangshi/ 推荐查看天气:https://www.0dtq.com/pingxiangshi/zuotian.html 推荐查看天气:https://www.0dtq.com/pingxiangshi/7tian.html 推荐查看天气:https://www.0dtq.com/pingxiangshi/10tian.html 推荐查看天气:https://www.0dtq.com/pingxiangshi/15tian.html 推荐查看天气:https://www.0dtq.com/pingxiangshi/40tian.html 推荐查看天气:https://www.0dtq.com/hainan/ 推荐查看天气:https://www.0dtq.com/hainan/zuotian.html 推荐查看天气:https://www.0dtq.com/hainan/7tian.html 推荐查看天气:https://www.0dtq.com/hainan/10tian.html 推荐查看天气:https://www.0dtq.com/hainan/15tian.html 推荐查看天气:https://www.0dtq.com/hainan/40tian.html 推荐查看天气:https://www.0dtq.com/haikou/ 推荐查看天气:https://www.0dtq.com/haikou/zuotian.html 推荐查看天气:https://www.0dtq.com/haikou/7tian.html 推荐查看天气:https://www.0dtq.com/haikou/10tian.html 推荐查看天气:https://www.0dtq.com/haikou/15tian.html 推荐查看天气:https://www.0dtq.com/haikou/40tian.html 推荐查看天气:https://www.0dtq.com/xiuying/ 推荐查看天气:https://www.0dtq.com/xiuying/zuotian.html 推荐查看天气:https://www.0dtq.com/xiuying/7tian.html 推荐查看天气:https://www.0dtq.com/xiuying/10tian.html 推荐查看天气:https://www.0dtq.com/xiuying/15tian.html 推荐查看天气:https://www.0dtq.com/xiuying/40tian.html 推荐查看天气:https://www.0dtq.com/longhua/