JavaScript 中的 AbortController:如何优雅地取消异步任务
在开发搜索框实时联想、文件上传进度控制或页面切换时中断数据请求等功能时,我们经常需要取消已经发出但不再需要的异步操作。以前用 XMLHttpRequest 可以直接调用 abort(),但换到 fetch 或一些返回 Promise 的异步函数后,取消就变得棘手。很多人在组件卸载后仍收到 setState 警告,或者因为旧请求覆盖新请求导致界面显示错误数据,根源就是没有真正取消异步任务。AbortController 是现代浏览器提供的标准方案,它通过一个信号对象将取消指令传递给 fetch、事件监听甚至自定义异步逻辑。这篇文章会从实际场景出发,讲清楚它的基本用法、如何配合 fetch 取消请求、如何监听取消事件、如何封装可复用的取消逻辑,以及处理超时和多个信号的组合场景。 先看一个不加取消的典型问题。假设你有一个输入框,每次输入都发起搜索请求: ```js let currentRequestId = 0; async function search(keyword) { const id = ++currentRequestId; const res = await fetch(`/api/search?q=${keyword}`); const data = await res.json(); if (id === currentRequestId) { renderResults(data); } } ``` 这种手动标记的方式能避免旧请求覆盖新结果,但网络请求依然在后台跑,浪费带宽和服务器资源。如果用户快速输入 10 个字符,就会产生 10 个请求,只有最后一个的结果被使用。 AbortController 的用法很直接。创建一个控制器实例,把它关联的 signal 传给 fetch 的 options: ```js const controller = new AbortController(); fetch('/api/data', { signal: controller.signal }) .then(res => res.json()) .then(data => console.log(data)) .catch(err => { if (err.name === 'AbortError') { console.log('请求被取消'); } else { console.error('其他错误', err); } }); // 在需要取消的时候调用 controller.abort(); ``` 调用 abort() 后,fetch 返回的 Promise 会 reject 一个 name 为 'AbortError' 的 DOMException。catch 里要区分主动取消和网络错误,避免把取消当成异常上报。 在搜索联想场景中,我们可以在每次新请求前取消上一个: ```js let controller = null; async function search(keyword) { if (controller) { controller.abort(); } controller = new AbortController(); try { const res = await fetch(`/api/search?q=${encodeURIComponent(keyword)}`, { signal: controller.signal }); const data = await res.json(); renderResults(data); } catch (err) { if (err.name !== 'AbortError') { showError(err); } } } ``` 这样每次输入都会取消前一个未完成的请求,只有最后一次的响应会被渲染。注意 controller 变量在闭包中共享,如果是在组件里,最好把它存在组件实例或 ref 上,并在组件卸载时也调用一次 abort,防止内存泄漏。 除了 fetch,AbortController 的 signal 还可以用于 addEventListener 的移除。以前移除事件监听必须用同一个函数引用,现在可以这样: ```js const controller = new AbortController(); const { signal } = controller; document.addEventListener('scroll', handleScroll, { signal }); // 需要移除时 controller.abort(); ``` 这比保存函数引用再 removeEventListener 更简洁,尤其在多个事件监听需要统一清理时特别有用。 有些场景需要同时处理超时和手动取消。一个常见的需求是请求超过 5 秒自动中断,同时用户离开页面也可以取消。AbortSignal 提供了静态方法 timeout 和 any 来组合信号: ```js const timeoutSignal = AbortSignal.timeout(5000); const userController = new AbortController(); const combinedSignal = AbortSignal.any([ timeoutSignal, userController.signal ]); fetch('/api/slow', { signal: combinedSignal }) .then(res => res.json()) .catch(err => { if (err.name === 'TimeoutError') { console.log('请求超时'); } else if (err.name === 'AbortError') { console.log('用户取消'); } }); ``` AbortSignal.timeout 会返回一个在指定毫秒后自动 abort 的 signal,并且 reject 的 error name 是 'TimeoutError'。AbortSignal.any 则接收一个 signal 数组,只要其中任意一个被 abort,组合信号就会 abort。这两个方法在现代浏览器中已经可用,Node.js 从 17.3 开始也支持 AbortController,18 以后支持 timeout 和 any。 如果项目需要兼容较老的浏览器或 Node 版本,可以使用 abort-controller 这个 polyfill,或者自己封装一个简单的取消令牌。但大多数现代项目已经可以直接使用原生 API。 在自定义异步函数中也可以利用 AbortSignal 实现取消。比如一个分批处理数据的函数: ```js async function processItems(items, signal) { for (const item of items) { if (signal.aborted) { throw new DOMException('Aborted', 'AbortError'); } await doSomething(item); } } const controller = new AbortController(); processItems(list, controller.signal).catch(err => { if (err.name === 'AbortError') { console.log('处理被中断'); } }); // 外部取消 controller.abort(); ``` 在循环中检查 signal.aborted 是一种协作式取消,适合那些不能直接传入 signal 的异步操作。更优雅的做法是监听 signal 的 abort 事件,在事件触发时清理资源或抛出异常: ```js function cancellableDelay(ms, signal) { return new Promise((resolve, reject) => { const timer = setTimeout(resolve, ms); signal.addEventListener('abort', () => { clearTimeout(timer); reject(new DOMException('Aborted', 'AbortError')); }, { once: true }); }); } ``` 这样任何传入 signal 的调用方都可以取消这个延时。 在 React 或 Vue 组件中,通常会在 useEffect 或 onUnmounted 里做清理。以 React 为例: ```js useEffect(() => { const controller = new AbortController(); fetch('/api/user', { signal: controller.signal }) .then(res => res.json()) .then(setUser) .catch(err => { if (err.name !== 'AbortError') { setError(err); } }); return () => controller.abort(); }, []); ``` 组件卸载时自动取消请求,避免了在已卸载组件上更新状态。 AbortController 不能直接取消已经进入处理阶段的 Promise,比如一个已经 resolve 的 Promise 或者纯计算任务。它本质上是一种信号机制,需要异步操作内部主动配合。对于 fetch、事件监听、Node.js 的 stream 等原生支持 signal 的 API,取消是开箱即用的。对于自己写的异步函数,需要手动检查 signal 状态或监听 abort 事件。 在使用 AbortController 时,一个容易忽略的点是 abort() 之后 signal 会永久处于 aborted 状态,不能再用于新的请求。所以每次请求都应该创建新的 AbortController 实例,而不是复用一个。另外,如果同时取消多个请求,可以共用一个 controller,但要注意它们会一起被取消。 Node.js 中的 http 模块从 v14.17 开始支持 signal,配合 fetch(Node 18+)或 undici 都可以使用。在 Express 等框架中,如果客户端断开连接,可以监听 req 的 'close' 事件来触发 abort,避免继续处理无用的请求: ```js app.get('/api/data', async (req, res) => { const controller = new AbortController(); req.on('close', () => controller.abort()); try { const result = await fetch('https://external.com/data', { signal: controller.signal }); const data = await result.json(); res.json(data); } catch (err) { if (err.name === 'AbortError') { console.log('客户端提前断开'); } else { res.status(500).json({ error: err.message }); } } }); ``` 这段代码在客户端关闭连接时取消对外部服务的请求,节省服务器资源。 AbortController 的兼容性现在已经很好,Chrome 66+、Firefox 57+、Safari 12.1+ 都支持。如果不需要支持 IE 或很老的移动端浏览器,可以直接使用。对于 TypeScript 项目,lib 中包含 DOM 或 Node 的类型定义即可,signal 的类型是 AbortSignal。 最后提一下和 axios 的配合。axios 从 v0.22.0 开始支持 AbortController,之前用的是 CancelToken。现在推荐直接用 signal: ```js const controller = new AbortController(); axios.get('/api/data', { signal: controller.signal }) .catch(err => { if (axios.isCancel(err)) { console.log('请求被取消'); } }); controller.abort(); ``` axios 会把 AbortError 包装成 CanceledError,用 axios.isCancel 判断更准确。如果同时使用 fetch 和 axios,统一用 AbortController 可以减少心智负担。 实际项目里,取消请求只是 AbortController 的一个用途。它同样适合管理事件监听的生命周期、取消流式读取、中断 WebSocket 连接前的准备操作等。把 signal 作为异步函数的可选参数传递下去,可以让整个调用链都支持取消,这在大型前端应用中尤其有用。 推荐查看天气:https://www.40tianqi.com/yajiang/zuotian.html 推荐查看天气:https://www.40tianqi.com/yajiang/7tian.html 推荐查看天气:https://www.40tianqi.com/yajiang/10tian.html 推荐查看天气:https://www.40tianqi.com/yajiang/15tian.html 推荐查看天气:https://www.40tianqi.com/yajiang/40tian.html 推荐查看天气:https://www.40tianqi.com/daofu/ 推荐查看天气:https://www.40tianqi.com/daofu/zuotian.html 推荐查看天气:https://www.40tianqi.com/daofu/7tian.html 推荐查看天气:https://www.40tianqi.com/daofu/10tian.html 推荐查看天气:https://www.40tianqi.com/daofu/15tian.html 推荐查看天气:https://www.40tianqi.com/daofu/40tian.html 推荐查看天气:https://www.40tianqi.com/luhuo/ 推荐查看天气:https://www.40tianqi.com/luhuo/zuotian.html 推荐查看天气:https://www.40tianqi.com/luhuo/7tian.html 推荐查看天气:https://www.40tianqi.com/luhuo/10tian.html 推荐查看天气:https://www.40tianqi.com/luhuo/15tian.html 推荐查看天气:https://www.40tianqi.com/luhuo/40tian.html 推荐查看天气:https://www.40tianqi.com/xinlong/ 推荐查看天气:https://www.40tianqi.com/xinlong/zuotian.html 推荐查看天气:https://www.40tianqi.com/xinlong/7tian.html 推荐查看天气:https://www.40tianqi.com/xinlong/10tian.html 推荐查看天气:https://www.40tianqi.com/xinlong/15tian.html 推荐查看天气:https://www.40tianqi.com/xinlong/40tian.html 推荐查看天气:https://www.40tianqi.com/dege/ 推荐查看天气:https://www.40tianqi.com/dege/zuotian.html 推荐查看天气:https://www.40tianqi.com/dege/7tian.html 推荐查看天气:https://www.40tianqi.com/dege/10tian.html 推荐查看天气:https://www.40tianqi.com/dege/15tian.html 推荐查看天气:https://www.40tianqi.com/dege/40tian.html 推荐查看天气:https://www.40tianqi.com/baiyu/ 推荐查看天气:https://www.40tianqi.com/baiyu/zuotian.html 推荐查看天气:https://www.40tianqi.com/baiyu/7tian.html 推荐查看天气:https://www.40tianqi.com/baiyu/10tian.html 推荐查看天气:https://www.40tianqi.com/baiyu/15tian.html 推荐查看天气:https://www.40tianqi.com/baiyu/40tian.html 推荐查看天气:https://www.40tianqi.com/shiqu/ 推荐查看天气:https://www.40tianqi.com/shiqu/zuotian.html 推荐查看天气:https://www.40tianqi.com/shiqu/7tian.html 推荐查看天气:https://www.40tianqi.com/shiqu/10tian.html 推荐查看天气:https://www.40tianqi.com/shiqu/15tian.html 推荐查看天气:https://www.40tianqi.com/shiqu/40tian.html 推荐查看天气:https://www.40tianqi.com/seda/ 推荐查看天气:https://www.40tianqi.com/seda/zuotian.html 推荐查看天气:https://www.40tianqi.com/seda/7tian.html 推荐查看天气:https://www.40tianqi.com/seda/10tian.html 推荐查看天气:https://www.40tianqi.com/seda/15tian.html 推荐查看天气:https://www.40tianqi.com/seda/40tian.html 推荐查看天气:https://www.40tianqi.com/litang/ 推荐查看天气:https://www.40tianqi.com/litang/zuotian.html 推荐查看天气:https://www.40tianqi.com/litang/7tian.html 推荐查看天气:https://www.40tianqi.com/litang/10tian.html 推荐查看天气:https://www.40tianqi.com/litang/15tian.html 推荐查看天气:https://www.40tianqi.com/litang/40tian.html 推荐查看天气:https://www.40tianqi.com/batang/ 推荐查看天气:https://www.40tianqi.com/batang/zuotian.html 推荐查看天气:https://www.40tianqi.com/batang/7tian.html 推荐查看天气:https://www.40tianqi.com/batang/10tian.html 推荐查看天气:https://www.40tianqi.com/batang/15tian.html 推荐查看天气:https://www.40tianqi.com/batang/40tian.html 推荐查看天气:https://www.40tianqi.com/xiangchengxian/ 推荐查看天气:https://www.40tianqi.com/xiangchengxian/zuotian.html 推荐查看天气:https://www.40tianqi.com/xiangchengxian/7tian.html 推荐查看天气:https://www.40tianqi.com/xiangchengxian/10tian.html 推荐查看天气:https://www.40tianqi.com/xiangchengxian/15tian.html 推荐查看天气:https://www.40tianqi.com/xiangchengxian/40tian.html 推荐查看天气:https://www.40tianqi.com/daocheng/ 推荐查看天气:https://www.40tianqi.com/daocheng/zuotian.html 推荐查看天气:https://www.40tianqi.com/daocheng/7tian.html 推荐查看天气:https://www.40tianqi.com/daocheng/10tian.html 推荐查看天气:https://www.40tianqi.com/daocheng/15tian.html 推荐查看天气:https://www.40tianqi.com/daocheng/40tian.html 推荐查看天气:https://www.40tianqi.com/derong/ 推荐查看天气:https://www.40tianqi.com/derong/zuotian.html 推荐查看天气:https://www.40tianqi.com/derong/7tian.html 推荐查看天气:https://www.40tianqi.com/derong/10tian.html 推荐查看天气:https://www.40tianqi.com/derong/15tian.html 推荐查看天气:https://www.40tianqi.com/derong/40tian.html 推荐查看天气:https://www.40tianqi.com/liangshan/ 推荐查看天气:https://www.40tianqi.com/liangshan/zuotian.html 推荐查看天气:https://www.40tianqi.com/liangshan/7tian.html 推荐查看天气:https://www.40tianqi.com/liangshan/10tian.html 推荐查看天气:https://www.40tianqi.com/liangshan/15tian.html 推荐查看天气:https://www.40tianqi.com/liangshan/40tian.html 推荐查看天气:https://www.40tianqi.com/xichang/ 推荐查看天气:https://www.40tianqi.com/xichang/zuotian.html 推荐查看天气:https://www.40tianqi.com/xichang/7tian.html 推荐查看天气:https://www.40tianqi.com/xichang/10tian.html 推荐查看天气:https://www.40tianqi.com/xichang/15tian.html 推荐查看天气:https://www.40tianqi.com/xichang/40tian.html 推荐查看天气:https://www.40tianqi.com/muli/ 推荐查看天气:https://www.40tianqi.com/muli/zuotian.html 推荐查看天气:https://www.40tianqi.com/muli/7tian.html 推荐查看天气:https://www.40tianqi.com/muli/10tian.html 推荐查看天气:https://www.40tianqi.com/muli/15tian.html 推荐查看天气:https://www.40tianqi.com/muli/40tian.html 推荐查看天气:https://www.40tianqi.com/yanyuan/ 推荐查看天气:https://www.40tianqi.com/yanyuan/zuotian.html 推荐查看天气:https://www.40tianqi.com/yanyuan/7tian.html 推荐查看天气:https://www.40tianqi.com/yanyuan/10tian.html 推荐查看天气:https://www.40tianqi.com/yanyuan/15tian.html 推荐查看天气:https://www.40tianqi.com/yanyuan/40tian.html 推荐查看天气:https://www.40tianqi.com/dechang/ 推荐查看天气:https://www.40tianqi.com/dechang/zuotian.html 推荐查看天气:https://www.40tianqi.com/dechang/7tian.html 推荐查看天气:https://www.40tianqi.com/dechang/10tian.html 推荐查看天气:https://www.40tianqi.com/dechang/15tian.html 推荐查看天气:https://www.40tianqi.com/dechang/40tian.html 推荐查看天气:https://www.40tianqi.com/huili/ 推荐查看天气:https://www.40tianqi.com/huili/zuotian.html 推荐查看天气:https://www.40tianqi.com/huili/7tian.html 推荐查看天气:https://www.40tianqi.com/huili/10tian.html 推荐查看天气:https://www.40tianqi.com/huili/15tian.html 推荐查看天气:https://www.40tianqi.com/huili/40tian.html 推荐查看天气:https://www.40tianqi.com/huidong/ 推荐查看天气:https://www.40tianqi.com/huidong/zuotian.html 推荐查看天气:https://www.40tianqi.com/huidong/7tian.html 推荐查看天气:https://www.40tianqi.com/huidong/10tian.html 推荐查看天气:https://www.40tianqi.com/huidong/15tian.html 推荐查看天气:https://www.40tianqi.com/huidong/40tian.html 推荐查看天气:https://www.40tianqi.com/ningnan/ 推荐查看天气:https://www.40tianqi.com/ningnan/zuotian.html 推荐查看天气:https://www.40tianqi.com/ningnan/7tian.html 推荐查看天气:https://www.40tianqi.com/ningnan/10tian.html 推荐查看天气:https://www.40tianqi.com/ningnan/15tian.html 推荐查看天气:https://www.40tianqi.com/ningnan/40tian.html 推荐查看天气:https://www.40tianqi.com/puge/ 推荐查看天气:https://www.40tianqi.com/puge/zuotian.html 推荐查看天气:https://www.40tianqi.com/puge/7tian.html 推荐查看天气:https://www.40tianqi.com/puge/10tian.html 推荐查看天气:https://www.40tianqi.com/puge/15tian.html 推荐查看天气:https://www.40tianqi.com/puge/40tian.html 推荐查看天气:https://www.40tianqi.com/butuo/ 推荐查看天气:https://www.40tianqi.com/butuo/zuotian.html 推荐查看天气:https://www.40tianqi.com/butuo/7tian.html 推荐查看天气:https://www.40tianqi.com/butuo/10tian.html 推荐查看天气:https://www.40tianqi.com/butuo/15tian.html 推荐查看天气:https://www.40tianqi.com/butuo/40tian.html 推荐查看天气:https://www.40tianqi.com/jinyang/ 推荐查看天气:https://www.40tianqi.com/jinyang/zuotian.html 推荐查看天气:https://www.40tianqi.com/jinyang/7tian.html 推荐查看天气:https://www.40tianqi.com/jinyang/10tian.html 推荐查看天气:https://www.40tianqi.com/jinyang/15tian.html 推荐查看天气:https://www.40tianqi.com/jinyang/40tian.html 推荐查看天气:https://www.40tianqi.com/zhaojue/ 推荐查看天气:https://www.40tianqi.com/zhaojue/zuotian.html 推荐查看天气:https://www.40tianqi.com/zhaojue/7tian.html 推荐查看天气:https://www.40tianqi.com/zhaojue/10tian.html 推荐查看天气:https://www.40tianqi.com/zhaojue/15tian.html 推荐查看天气:https://www.40tianqi.com/zhaojue/40tian.html 推荐查看天气:https://www.40tianqi.com/xide/ 推荐查看天气:https://www.40tianqi.com/xide/zuotian.html 推荐查看天气:https://www.40tianqi.com/xide/7tian.html 推荐查看天气:https://www.40tianqi.com/xide/10tian.html 推荐查看天气:https://www.40tianqi.com/xide/15tian.html 推荐查看天气:https://www.40tianqi.com/xide/40tian.html 推荐查看天气:https://www.40tianqi.com/mianning/ 推荐查看天气:https://www.40tianqi.com/mianning/zuotian.html 推荐查看天气:https://www.40tianqi.com/mianning/7tian.html 推荐查看天气:https://www.40tianqi.com/mianning/10tian.html 推荐查看天气:https://www.40tianqi.com/mianning/15tian.html 推荐查看天气:https://www.40tianqi.com/mianning/40tian.html 推荐查看天气:https://www.40tianqi.com/yuexi/ 推荐查看天气:https://www.40tianqi.com/yuexi/zuotian.html 推荐查看天气:https://www.40tianqi.com/yuexi/7tian.html 推荐查看天气:https://www.40tianqi.com/yuexi/10tian.html 推荐查看天气:https://www.40tianqi.com/yuexi/15tian.html 推荐查看天气:https://www.40tianqi.com/yuexi/40tian.html 推荐查看天气:https://www.40tianqi.com/ganluo/ 推荐查看天气:https://www.40tianqi.com/ganluo/zuotian.html 推荐查看天气:https://www.40tianqi.com/ganluo/7tian.html 推荐查看天气:https://www.40tianqi.com/ganluo/10tian.html 推荐查看天气:https://www.40tianqi.com/ganluo/15tian.html 推荐查看天气:https://www.40tianqi.com/ganluo/40tian.html 推荐查看天气:https://www.40tianqi.com/meigu/ 推荐查看天气:https://www.40tianqi.com/meigu/zuotian.html 推荐查看天气:https://www.40tianqi.com/meigu/7tian.html 推荐查看天气:https://www.40tianqi.com/meigu/10tian.html 推荐查看天气:https://www.40tianqi.com/meigu/15tian.html 推荐查看天气:https://www.40tianqi.com/meigu/40tian.html 推荐查看天气:https://www.40tianqi.com/leibo/ 推荐查看天气:https://www.40tianqi.com/leibo/zuotian.html 推荐查看天气:https://www.40tianqi.com/leibo/7tian.html 推荐查看天气:https://www.40tianqi.com/leibo/10tian.html 推荐查看天气:https://www.40tianqi.com/leibo/15tian.html 推荐查看天气:https://www.40tianqi.com/leibo/40tian.html 推荐查看天气:https://www.40tianqi.com/guiyang/ 推荐查看天气:https://www.40tianqi.com/guiyang/zuotian.html 推荐查看天气:https://www.40tianqi.com/guiyang/7tian.html 推荐查看天气:https://www.40tianqi.com/guiyang/10tian.html 推荐查看天气:https://www.40tianqi.com/guiyang/15tian.html 推荐查看天气:https://www.40tianqi.com/guiyang/40tian.html 推荐查看天气:https://www.40tianqi.com/nanming/ 推荐查看天气:https://www.40tianqi.com/nanming/zuotian.html 推荐查看天气:https://www.40tianqi.com/nanming/7tian.html 推荐查看天气:https://www.40tianqi.com/nanming/10tian.html 推荐查看天气:https://www.40tianqi.com/nanming/15tian.html 推荐查看天气:https://www.40tianqi.com/nanming/40tian.html 推荐查看天气:https://www.40tianqi.com/yunyan/ 推荐查看天气:https://www.40tianqi.com/yunyan/zuotian.html 推荐查看天气:https://www.40tianqi.com/yunyan/7tian.html