news 2026/9/7 15:58:15

Axios 请求取消机制详解:AbortController 与 CancelToken 的实现原理及实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Axios 请求取消机制详解:AbortController 与 CancelToken 的实现原理及实战

Axios 请求取消机制详解:AbortController 与 CancelToken 的实现原理及实战

【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios

Axios 从 v0.22.0 起同时支持基于AbortController的新一代取消方式和已废弃的CancelTokenAPI。本文以 Axios 官方取消请求文档为主体,完整覆盖两种 API 的用法、取消错误的判定方式与 TypeScript 类型支持,并深入 lib/cancel 目录下的源码,解析取消信号在调度层与 HTTP/XHR 适配器中的真实流转路径,帮助你在浏览器和 Node.js 中正确、可靠地中断在途请求并妥善处理取消引发的错误。

一、AbortController:推荐的取消方式

从 v0.22.0 起,Axios 支持使用 Web 标准的AbortController以简洁的方式取消请求。该功能在浏览器和 Node.js(使用支持AbortController的版本)中均可用。用法是创建一个AbortController实例,并把它的signal传入请求配置的signal选项:

const controller = new AbortController(); axios .get('/foo/bar', { signal: controller.signal, }) .then(function (response) { //... }); // 取消请求 controller.abort();

这是与平台标准fetchsignal参数一致的语义,因此可以直接复用浏览器原生信号、AbortSignal.timeout()或 React/Vue 组件卸载时创建的控制器,无需引入 Axios 私有 API。

源码中的取消检查点

signal并不是直接透传给底层请求对象的孤立选项,而是贯穿了请求生命周期的多个检查点。在调度层 lib/core/dispatchRequest.js 中,throwIfCancellationRequested函数同时检查两种取消来源:

function throwIfCancellationRequested(config) { if (config.cancelToken) { config.cancelToken.throwIfRequested(); } if (config.signal && config.signal.aborted) { throw new CanceledError(null, config); } }

它被调用在三个关键时机:

  1. 请求发出前(dispatchRequest.js 第 41 行):若signal在请求开始前就已处于 aborted 状态,直接抛出CanceledError,底层适配器根本不会被执行——这正是文档中"已取消状态下发起的请求会立即取消、不会尝试实际网络请求"的实现来源;
  2. 适配器成功返回后(dispatchRequest.js):在transformResponse处理响应数据前再检查一次,防止慢速响应的数据被继续加工;
  3. 适配器失败路径中(dispatchRequest.js):若失败原因不是取消错误,仍补一次取消检查,把"响应错误"重新归类为取消。

在 Node.js 的 HTTP 适配器 lib/adapters/http.js 中,信号被真正挂接到原生请求上:

if (config.cancelToken || config.signal) { config.cancelToken && config.cancelToken.subscribe(abort); if (config.signal) { config.signal.aborted ? abort() : config.signal.addEventListener('abort', abort); } }

可见 Axios 对两种取消机制做了统一归并:cancelToken通过订阅(subscribe)接入,signal通过标准的abort事件接入,且都先判断是否已处于取消状态。请求结束时两者都会被解绑(unsubscribe/removeEventListener,见 http.js 与浏览器适配器的 xhr.js),避免事件监听器泄漏。

浏览器 XHR 适配器同样处理这两种信号,在 lib/adapters/xhr.js 中订阅取消并在done()中清理监听。

二、CancelToken:已废弃但仍可用的经典 API

Axios 也保留了CancelTokenAPI 来取消请求。该 API已废弃,将在下一个主版本中移除,官方建议改用AbortController;但对存量代码而言,它仍是当前版本中功能完整的方案。

方式一:CancelToken.source() 工厂方法

最直观的用法是CancelToken.source()工厂方法,它返回{ token, cancel }对象——把token放入请求配置,把cancel函数留给业务代码:

const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios .get('/user/12345', { cancelToken: source.token, }) .catch(function (thrown) { if (axios.isCancel(thrown)) { console.log('Request canceled', thrown.message); } else { // 处理错误 } }); axios.post( '/user/12345', { name: 'new name', }, { cancelToken: source.token, } ); // 取消请求(message 参数可选) source.cancel('Operation canceled by the user.');

对应源码在 lib/cancel/CancelToken.js 的静态方法source():它通过执行函数捕获内部的cancel函数并暴露出来,返回tokencancel的配对。

方式二:执行函数(executor)模式

也可以向CancelToken构造函数传入执行函数来创建令牌,适合"取消时机由复杂逻辑决定"的场景:

const CancelToken = axios.CancelToken; let cancel; axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { // 执行函数接收一个 cancel 函数作为参数 cancel = c; }), }); // 取消请求 cancel();

构造函数对参数做了严格校验:executor必须是函数,否则抛出TypeError('executor must be a function.')(CancelToken.js)。此外注意构造函数内部是同步调用executorcancel必须在构造完成后(通常是请求发出后的回调里)才被赋值使用,不能在构造期间直接调用。

底层辅助方法:subscribe / unsubscribe / toAbortSignal

CancelToken还为旧版集成暴露了一些底层辅助方法,用于把取消令牌桥接到其他监听体系或标准AbortSignal

const source = axios.CancelToken.source(); const listener = (cancel) => { console.log(cancel.message); }; source.token.subscribe(listener); const signal = source.token.toAbortSignal(); // 将 `signal` 传给接受 AbortSignal 的 API。 source.cancel('Operation canceled by the user.'); source.token.unsubscribe(listener);

从源码看这三个方法的实现要点(CancelToken.js):

  • subscribe(listener):若令牌已被取消(this.reason存在),则同步立即回调listener(this.reason);否则把监听器压入_listeners数组,等待取消发生时统一触发(触发后_listeners置为null,防止重复派发);
  • unsubscribe(listener):从监听数组中移除指定函数;
  • toAbortSignal():内部新建一个AbortController,通过subscribe把取消原因桥接为controller.abort(err),并且给返回的signal挂上了自定义的unsubscribe方法(() => this.unsubscribe(abort)),供如 Axios 适配器composeSignals一类的组合逻辑做清理。

三、取消错误:CanceledError 与 isCancel

被取消的请求会以axios.CanceledError拒绝(reject)。旧版导出axios.Cancelaxios.CanceledError的别名(兼容旧代码),取消错误上还带有__CANCEL__标记,供axios.isCancel判断使用。

这些结论均可在源码中逐一验证:

  • lib/cancel/CanceledError.js:CanceledError继承自AxiosError,构造函数写入错误码AxiosError.ERR_CANCELEDname = 'CanceledError',以及关键标记this.__CANCEL__ = true;当 message 为空时默认使用字符串'canceled'
  • lib/cancel/isCancel.js:isCancel(value)的实现就是!!(value && value.__CANCEL__),即基于__CANCEL__属性做鸭子类型判断,这也是为什么toAbortSignal桥接等路径必须确保最终 reject 的是带该标记的错误对象;
  • lib/axios.js:axios.CanceledErroraxios.CancelTokenaxios.isCancel三个成员的挂载位置,以及第 63 行axios.Cancel = axios.CanceledError的别名声明。

单元测试 tests/unit/cancel/canceledError.test.js 也印证了行为细节:默认消息为CanceledError: canceled,自定义消息原样保留;且node:util/typesisNativeError能识别它为原生错误,方便在通用错误处理管道中与普通Error一致对待。

TypeScript 下的 isCancel 类型收窄

在 TypeScript 中,isCancel<T, D, P>()在收窄unknown错误类型时,会保留响应数据、请求数据和查询参数的泛型:

interface SearchResponse { results: string[]; } interface RequestBody { includeArchived: boolean; } interface SearchParams { query: string; } try { await axios.get("/search"); } catch (error) { if (axios.isCancel<SearchResponse, RequestBody, SearchParams>(error)) { error.response?.data; // SearchResponse | undefined error.config?.data; // RequestBody | undefined error.config?.params; // SearchParams | undefined } }

这一能力的根源是CanceledError构造时携带了configrequest参数(见 CanceledError.js),而AxiosError基类又把二者挂到了错误实例的config/request上,因此取消错误对象本身就携带了请求上下文,类型系统得以在此基础上做精确收窄。

四、一个令牌取消多个请求与"预取消"行为

你可以使用同一个取消令牌或AbortController取消多个请求。如果在 Axios 请求开始时取消令牌已处于已取消状态,则请求会立即被取消,不会尝试发起实际的网络请求。

这一行为的底层依据在前文已说明:

  • AbortController路径,throwIfCancellationRequested在请求发出前检查config.signal.aborted,直接抛出CanceledError(dispatchRequest.js);
  • CancelToken路径,token.throwIfRequested()在已取消(存在reason)时立即抛出该CanceledError(CancelToken.js);
  • 适配层对"已取消"的信号同样做了短路处理:signal.aborted ? abort() : addEventListener(...)(http.js),subscribe对已取消令牌也立即同步回调监听器(CancelToken.js)。

因此批量取消的典型模式是:为一批并发请求共享一个source/controller,需要整体中断时调用一次cancel(message)/abort(),所有在途与未发出的请求都会收到带统一 message 的CanceledError;已经发出且已完成的请求不受影响。

取消与超时的归并处理

值得补充的是,在 Node.js 适配器的流式场景下,取消还会与超时错误发生交互。从 lib/adapters/http.js 的结构看,abortEmitteronce('abort')统一驱动 reject;流被中途断开时以CanceledError('Request stream has been aborted', config, req)拒绝(http.js)。浏览器端取消相关的行为另有专项测试覆盖,可参见 tests/browser/cancel.browser.test.js 与 tests/browser/cancelToken.browser.test.js,单元测试侧则包括 tests/unit/cancel/isCancel.test.js。

五、两种 API 的选择建议

维度AbortControllerCancelToken
状态推荐(Web/Node 标准)已废弃,将在下一个主版本移除
配置项signal: controller.signalcancelToken: source.token
触发取消controller.abort(reason?)source.cancel(message?)
与平台 API 互通可直接复用原生AbortSignal需经token.toAbortSignal()桥接
多请求共享共享同一controller共享同一source.token
判定取消错误axios.isCancel(error)axios.isCancel(error)(相同)

迁移路径很直接:把CancelToken.source()换成new AbortController(),把cancelToken: source.token换成signal: controller.signal,把source.cancel(msg)换成controller.abort(msg)——错误判定代码(axios.isCancel/axios.CanceledError)无需改动,因为两条路径最终都归并到同一个CanceledError__CANCEL__标记上。

【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 15:57:34

Flutter在OpenHarmony上的表单实战:发起组队功能完整实现

前几篇写完项目的整体骨架、路由和房间列表之后&#xff0c;总算要碰一个真正需要“动手填”的界面了。这一篇我们聚焦剧本杀组队App里最核心的入口&#xff1a;发起组队表单。说实话&#xff0c;在很多教程里表单通常被一笔带过&#xff0c;好像就是几个输入框堆在一起而已&am…

作者头像 李华
网站建设 2026/9/7 15:57:32

Unity自定义Inspector显示名:用CustomLabel特性实现字段名与代码解耦

前阵子项目里策划丢过来一个问题&#xff1a;“你这个 speed 到底是移动速度还是攻击速度&#xff1f;我在Inspector里改来改去总怕改错。”我打开编辑器一看&#xff0c;字段名是 m_MoveSpeed &#xff0c;确实是直接暴露出来了。代码命名规范要求字段带前缀没问题&#x…

作者头像 李华
网站建设 2026/9/7 15:57:06

从UMD到gRPC:AI训练中GPU指令翻译官的服务化实战

搞AI训练的哥们儿应该都有这种感觉&#xff1a;模型写起来不难&#xff0c;难的是让计算真正跑到GPU上。这一层“让计算跑起来”的衔接&#xff0c;通常不是AI框架直接操作显卡&#xff0c;而是框架调用GPU驱动&#xff0c;驱动再把算子请求翻译成硬件指令。今天聊的就是这条链…

作者头像 李华
网站建设 2026/9/7 15:53:02

Buzz 语音转文字:离线转录快速上手指南

Buzz 语音转文字&#xff1a;离线转录快速上手指南 【免费下载链接】buzz Buzz transcribes and translates audio offline on your personal computer. Powered by OpenAIs Whisper. 项目地址: https://gitcode.com/GitHub_Trending/buz/buzz Buzz 是一款完全本地的离线…

作者头像 李华
网站建设 2026/9/7 15:51:55

Scala样例类与模式匹配:从求面积到工程最佳实践

写了几年代码&#xff0c;看过不少Scala教程&#xff0c;真正让我觉得“这门语言有点东西”的&#xff0c;恰恰是“样例类&#xff08;case class&#xff09; 模式匹配&#xff08;pattern matching&#xff09;”这种看起来很基础、很想当然的组合。很多人入门时写过Circle、…

作者头像 李华