axios 拦截器完全指南:基于源码解析 use/eject/clear、执行顺序与同步执行机制

axios 拦截器完全指南:基于源码解析 use/eject/clear、执行顺序与同步执行机制 axios 拦截器完全指南基于源码解析 use/eject/clear、执行顺序与同步执行机制【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios拦截器Interceptors是 axios 中最核心的请求/响应中间层机制作用类似 Express.js 的中间件它是在请求发出前和响应接收后都会被执行的函数广泛用于鉴权头注入、日志埋点、请求参数改写与响应结构归一化。本文以 axios 仓库中的官方文档 interceptors.md 为主体骨架并结合 lib/core/Axios.js 与 lib/core/InterceptorManager.js 的源码实现完整讲解拦截器的注册、移除、同步执行、错误处理、条件执行runWhen与执行顺序LIFO/FIFO的全部细节读完后可直接在生产环境中正确配置多拦截器链路并理解其底层调度逻辑。拦截器的基本用法axios 的每个实例包括全局默认实例都持有两组独立的拦截器管理器axios.interceptors.request与axios.interceptors.response二者由 Axios 类构造函数 中创建的InterceptorManager实例承载。基本用法如下// 添加请求拦截器 axios.interceptors.request.use( function (config) { // 在请求发出前对 config 做任何处理加头、改 URL 等 return config; }, function (error) { // 处理请求阶段的错误 return Promise.reject(error); } ); // 添加响应拦截器 axios.interceptors.response.use( function (response) { // 任何 2xx 状态码都会触发此函数 // 对响应数据做转换、统一包装等 return response; }, function (error) { // 非 2xx 状态码或网络错误会触发此函数 return Promise.reject(error); } );从 TypeScript 类型定义看index.d.tsuse的完整签名为export interface AxiosInterceptorOptions { synchronous?: boolean; runWhen?: ((config: InternalAxiosRequestConfig) boolean) | null; } type AxiosRequestInterceptorUseT ( onFulfilled?: AxiosInterceptorFulfilledT | null, onRejected?: AxiosInterceptorRejected | null, options?: AxiosInterceptorOptions ) number; // 返回值是用于后续 eject 的数字 id关键要点请求拦截器的onFulfilled接收InternalAxiosRequestConfig必须返回 config同步返回或返回 Promise 均可响应拦截器的onFulfilled在状态码落入 2xx 区间时触发onRejected在状态码超出 2xx 或发生请求错误时触发use的返回值是一个数字id这是后续移除拦截器的凭证实现见 InterceptorManager.use它把fulfilled、rejected、synchronous、runWhen打包成一个 handler 对象压入handlers数组并返回自增的id。移除拦截器eject 与 clear有两种移除方式通过eject移除单个拦截器需要保存use返回的 id或通过clear清空整组拦截器。// 移除单个请求拦截器 const myInterceptor axios.interceptors.request.use(function () { /*...*/ }); axios.interceptors.request.eject(myInterceptor); // 移除单个响应拦截器 const myInterceptor axios.interceptors.response.use(function () { /*...*/ }); axios.interceptors.response.eject(myInterceptor);清空整组拦截器的示例const instance axios.create(); instance.interceptors.request.use(function () { /*...*/ }); instance.interceptors.request.clear(); // 移除该实例上所有请求拦截器 instance.interceptors.response.use(function () { /*...*/ }); instance.interceptors.response.clear(); // 移除该实例上所有响应拦截器源码层面InterceptorManager.eject 并不会立刻从数组中物理删除元素而是把对应位置置为null称为“墓碑” tombstone遍历时会跳过null项只有当移除的是数组末尾的墓碑时才调用trimHandlers收缩数组。tests/unit/core/InterceptorManager.test.js 用 1 万次use/eject循环验证了墓碑不会无限累积也验证了“在handlers数组被整体替换后旧 id 会被正确作废”的边界行为。而 clear() 则是直接重建一个空数组。默认异步行为与 synchronous 选项默认情况下请求拦截器被假定为异步的axios 会为拦截器创建 Promise如果你的拦截器是纯同步代码而主线程恰好被阻塞请求就会被排到调用栈的末尾产生不必要的延迟。为此use的第三个参数支持{ synchronous: true }选项告知 axios 以同步方式执行拦截器代码避免请求执行延迟axios.interceptors.request.use( function (config) { config.headers.test I am only a header!; return config; }, null, { synchronous: true } );在 Axios._request 中可以看到两条完全不同的执行路径异步路径默认把dispatchRequest与所有请求/响应拦截器组装成一条 Promise 链——chain [dispatchRequest, undefined]先unshift请求拦截器、再push响应拦截器然后while (i len) promise promise.then(chain[i], chain[i])逐对挂接同步路径所有请求拦截器均声明synchronous: true时用while循环同步顺序调用每个请求拦截器的onFulfilled出错时同步调用其onRejected最后才同步调用dispatchRequest之后再以 Promise 链挂接响应拦截器。判断条件是synchronousRequestInterceptors synchronousRequestInterceptors interceptor.synchronousAxios.js即只有当全部请求拦截器都标记为同步时整条请求链才走同步路径。同步拦截器的错误处理规则同步请求拦截器抛出异常时axios 会调用该拦截器配对的onRejected处理器并停止执行剩余的请求拦截器。此时的行为取决于onRejected的返回方式若onRejected正常返回包括返回undefined或一个已兑现的 Promise则认为错误已被处理axios 用最后一个有效 config继续发出请求onRejected的返回值不会替换该 config若要阻止请求发出应省略 rejection 处理器或让其抛出异常 / 返回一个被拒绝的 Promise该错误随后会流经响应阶段的 rejection 拦截器。当校验逻辑必须阻断请求时应返回被拒绝的 Promiseaxios.interceptors.request.use( function validate(config) { if (!config.headers.has(Authorization)) { throw new Error(Authorization is required); } return config; }, function rejectInvalidRequest(error) { return Promise.reject(error); }, { synchronous: true } );而只用于日志的 rejection 处理器可以正常返回以保持原有的“继续发出请求”行为axios.interceptors.request.use( function prepare(config) { throw new Error(Optional preparation failed); }, function logPreparationFailure(error) { console.warn(error); // 正常返回 ⇒ 使用最后一个有效 config 继续派发请求 }, { synchronous: true } );这与 Axios.js 同步分支 的实现对应onRejected的结果若为 thenable 且非拒绝态则Promise.resolve(rejectedResult).then(() dispatchRequest(newConfig))若onRejected本身又抛错则Promise.reject(rejectedError)终止派发。使用 runWhen 做条件执行如果希望某个拦截器只在满足特定运行时条件时才执行可以在 options 中传入runWhen函数。仅当runWhen返回false时拦截器才会被跳过runWhen会被传入当前的 config 对象也可以自行绑定额外参数。这在“某个异步拦截器只在特定场景需要运行”时非常有用function onGetCall(config) { return config.method get; } axios.interceptors.request.use( function (config) { config.headers.test special get headers; return config; }, null, { runWhen: onGetCall } );源码中对应的过滤逻辑位于 Axios._request在构建拦截器链之前对每个请求拦截器执行if (typeof interceptor.runWhen function interceptor.runWhen(config) false) return;被过滤掉的拦截器根本不会进入执行链。拦截器执行顺序LIFO 与 FIFO::: warning 请求拦截器与响应拦截器的执行顺序相反请求拦截器按逆序执行LIFO — 后入先出最后添加的请求拦截器最先执行响应拦截器按添加顺序执行FIFO — 先入先出最先添加的响应拦截器最先执行。 :::官方文档给出的完整执行顺序示例3 个请求 3 个响应拦截器const instance axios.create(); const interceptor (id) (base) { console.log(id); return base; }; instance.interceptors.request.use(interceptor(Request Interceptor 1)); instance.interceptors.request.use(interceptor(Request Interceptor 2)); instance.interceptors.request.use(interceptor(Request Interceptor 3)); instance.interceptors.response.use(interceptor(Response Interceptor 1)); instance.interceptors.response.use(interceptor(Response Interceptor 2)); instance.interceptors.response.use(interceptor(Response Interceptor 3)); // 控制台输出 // Request Interceptor 3 // Request Interceptor 2 // Request Interceptor 1 // [HTTP request is made] // Response Interceptor 1 // Response Interceptor 2 // Response Interceptor 3从源码结构看这个“相反顺序”由transitional.legacyInterceptorReqResOrdering开关控制请求拦截器先被按注册顺序收集进requestInterceptorChain随后若该开关为truelib/defaults/transitional.js 中默认即为true则requestInterceptorChain.unshift(...)将整条链翻转从而实现 LIFO响应拦截器则始终push保持 FIFO见 Axios.js。若把legacyInterceptorReqResOrdering显式设为false请求拦截器将按注册顺序执行这是官方预留的行为迁移开关。多拦截器链路的行为规则同一条链路上可以同时挂载多个拦截器以下规则在链上始终成立按此顺序每个拦截器都会被执行请求拦截器按逆序LIFO执行响应拦截器按添加顺序FIFO执行最终只返回最后一个拦截器的结果每个拦截器都能收到其前一个处理器的结果当某个onFulfilled抛出异常时后续的onFulfilled不会被调用后续的onRejected会被调用一旦错误被捕获并处理后续链上的onFulfilled会再次被调用与普通 Promise 链的行为一致。这些行为在 tests/smoke/esm/tests/interceptors.smoke.test.js 中有可运行的验证例如两个响应拦截器先后执行n 1与n * 10得到20直接证明了响应拦截器按注册顺序串联请求拦截器抛出blocked-by-interceptor错误后请求根本没有被派发getCalls()长度为 0错误原样冒泡到调用方eject掉请求拦截器后其注入的X-Ejected头不再出现在最终请求中。浏览器端的集成测试见 tests/browser/interceptors.browser.test.js。源码与测试索引关注点位置拦截器管理器use/eject/clear/forEach、墓碑回收lib/core/InterceptorManager.js拦截器链构建与同步/异步双路径lib/core/Axios.jslegacyInterceptorReqResOrdering等过渡配置默认值lib/defaults/transitional.jsTypeScript 类型AxiosInterceptorOptions等index.d.ts管理器边界行为单元测试墓碑、id 失效tests/unit/core/InterceptorManager.test.js执行顺序/eject/错误传播冒烟测试tests/smoke/esm/tests/interceptors.smoke.test.js浏览器端拦截器测试tests/browser/interceptors.browser.test.js以上路径可直接在仓库中打开核对若需要深入理解拦截器的调度细节建议从 InterceptorManager.test.js 的“墓碑累积”与“迭代中延迟压缩”两个用例入手它们精确刻画了并发eject/use时 handlers 数组的压缩时机。【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考