axios 拦截器(Interceptors)指南:请求/响应拦截、执行顺序与同步模式的源码级解析
【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios
拦截器是 axios 中用于拦截并修改 HTTP 请求与响应的核心机制,作用类似 Express.js 的中间件:请求拦截器在请求发出前运行,响应拦截器在响应返回后(或出错时)运行,常用于日志记录、修改请求头、统一处理响应数据与错误。读完本文,你将掌握拦截器的注册、移除、synchronous与runWhen两个高级选项的用法,以及请求/响应拦截器"相反的"执行顺序(LIFO 与 FIFO),并能从 axios 源码层面理解拦截链是如何构建和执行的。
一、基本用法:注册请求与响应拦截器
axios 实例提供interceptors.request与interceptors.response两个入口,均通过use(fulfilled, rejected, options)注册拦截器:
// 注册请求拦截器 axios.interceptors.request.use( function (config) { // 请求发出前对 config 做任何处理 return config; }, function (error) { // 处理请求阶段的错误 return Promise.reject(error); } ); // 注册响应拦截器 axios.interceptors.response.use( function (response) { // 任何 2xx 状态码都会触发该函数 return response; }, function (error) { // 任何非 2xx 状态码都会触发该函数 return Promise.reject(error); } );要点说明:
- 请求拦截器的
fulfilled收到的是配置对象config,必须返回(可能被修改过的)config; - 响应拦截器的
fulfilled只在状态码落在2xx 区间时触发,其余状态码走rejected; - 全局
axios与通过axios.create()创建的实例各自拥有独立的拦截器集合,互不影响——从源码结构看,这一点由 Axios 构造函数 直接体现:每个实例在构造时各自new InterceptorManager()分别创建interceptors.request和interceptors.response两个管理器。
二、移除拦截器:eject与clear
移除单个拦截器
use()的返回值是一个自增 ID,将其传给eject(id)即可精确移除对应拦截器:
// 移除请求拦截器 const myInterceptor = axios.interceptors.request.use(function () { /*...*/ }); axios.interceptors.request.eject(myInterceptor); // 移除响应拦截器 const myInterceptor = axios.interceptors.response.use(function () { /*...*/ }); axios.interceptors.response.eject(myInterceptor);清空所有拦截器
调用interceptors.request.clear()/interceptors.response.clear()可移除全部拦截器,常用于测试隔离或实例重置:
const instance = axios.create(); instance.interceptors.request.use(function () { /*...*/ }); instance.interceptors.request.clear(); // 移除所有请求拦截器 instance.interceptors.response.use(function () { /*...*/ }); instance.interceptors.response.clear(); // 移除所有响应拦截器源码视角:eject的"墓碑"机制与 ID 失效规则
拦截器的增删逻辑集中在 InterceptorManager。use()从内部计数器nextId++分配 ID 并push到handlers数组(use 实现);eject(id)并不立即收缩数组,而是把对应位置置为null(eject 实现):
- 只有当被移除的是末尾元素时才调用
trimHandlers收缩数组,中间的空位(源码注释称"tombstone")由forEach遍历时跳过null来规避; - 遍历(
iterationDepth > 0)期间不会压缩数组,避免在迭代长度快照过程中复用索引,遍历结束后才统一trimHandlers并重新同步内部handlerEntries(forEach 实现); - 如果用户代码把
handlers数组整体替换(它是公开属性),旧 ID 会被判定为"已失效",再次eject时静默忽略,不会误删新注册的拦截器。
这些边界行为的回归测试见 InterceptorManager 单元测试,其中覆盖了"1 万次 use/eject 后不累积墓碑"、"eject 后 ID 不复用"、"handlers 被替换后旧 ID 失效"等场景。
三、同步与异步:synchronous选项
请求拦截器默认被当作异步处理:axios 内部会为其创建一条 Promise 链,即使你的拦截器是纯同步代码,请求也会被推到调用栈末尾执行。当主线程被阻塞时,这可能引入不必要的延迟。
如果确认请求拦截器是同步的,可以在第三个参数(options 对象)中传入{ synchronous: true },让 axios 同步执行拦截器代码,避免延迟:
axios.interceptors.request.use( function (config) { config.headers.test = "I am only a header!"; return config; }, null, { synchronous: true } );同步模式下的执行链
在 Axios._request 中,所有请求拦截器都会先被标记synchronous,只有当所有注册的请求拦截器都声明了synchronous: true时,synchronousRequestInterceptors才为true,此时 axios 走同步分支:
- 依次同步调用每个拦截器的
onFulfilled,直接把返回值作为下一个拦截器的输入newConfig; - 全部执行完毕后同步调用
dispatchRequest发出请求,再按顺序挂接响应拦截器链(L250-L255)。
只要有一个请求拦截器未声明同步(或需要runWhen过滤后仍留有一个非同步拦截器),整条链就退回promise.then(...)的 Promise 链式执行模式(L196-L208)。
同步拦截器中的错误处理
当同步请求拦截器抛出错误时,行为规则是(与 Axios.js 同步分支 的实现一一对应):
- axios 调用与该拦截器配对的
onRejected处理器,并停止执行后续请求拦截器; - 若
onRejected正常返回——包括返回undefined或一个已解决的 Promise——该错误被视为"已处理",axios 将用最后一个有效的配置照常发出请求;注意:处理器的返回值不会替换配置对象; - 若要阻止请求发出,要么不提供
onRejected,要么让onRejected抛出错误 / 返回被拒绝的 Promise——此时错误转为终止性错误,继续流向响应侧的拦截器链(源码中即promise = Promise.reject(error)后进入响应拦截器链); - 特例:若
onRejected返回一个 thenable,源码会Promise.resolve(rejectedResult).then(...)后仍继续dispatchRequest,即"等待异步善后完成后照发请求"。
示例一:校验类拦截器——缺Authorization头则拒绝,且错误继续向上传播:
axios.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 } );示例二:只记录日志、不阻断请求的拦截器——处理器正常返回即可保持"继续发送"的语义:
axios.interceptors.request.use( function prepare(config) { throw new Error("Optional preparation failed"); }, function logPreparationFailure(error) { console.warn(error); // 正常返回:axios 将使用最后一个有效配置发出请求 }, { synchronous: true } );四、条件执行:runWhen选项
如果拦截器只需在满足特定运行时条件时才执行,可以在 options 中传入runWhen函数。当且仅当runWhen返回false时,该拦截器被跳过。runWhen会以当前请求的配置对象为参数被调用(注意:你也可以通过this绑定额外参数)。这对"仅在特定场景下运行的异步拦截器"特别有用,因为它能在请求阶段就把它排除出执行链,而不是等执行时再判断。
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.js):
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) { return; // 该拦截器不进入本次请求的执行链 } // ... });两个细节值得注意:
runWhen只作用于请求拦截器(响应拦截器链没有runWhen过滤);- 即使某拦截器声明了
synchronous: true,若它被runWhen跳过,则不参与"全部同步"的判定,同步分支依然可以成立。
五、执行顺序:请求 LIFO,响应 FIFO
警告:请求与响应拦截器的执行顺序是相反的。
- 请求拦截器按注册顺序的逆序执行(LIFO — 后注册先执行):最后注册的请求拦截器最先运行;
- 响应拦截器按注册顺序执行(FIFO — 先注册先执行):最先注册的响应拦截器最先运行。
下面这个例子展示了三个请求拦截器 + 三个响应拦截器时的完整执行顺序:
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 请求发出] // Response Interceptor 1 // Response Interceptor 2 // Response Interceptor 3源码视角:unshift与push
这个"洋葱式"顺序直接来自 Axios.js 的建链逻辑:请求拦截器通过requestInterceptorChain.unshift(...)头插,响应拦截器通过responseInterceptorChain.push(...)尾插,于是请求侧先注册的反而排在链的尾部。
一个重要的版本行为提示:该处实际受transitional.legacyInterceptorReqResOrdering开关控制——在 transitional 默认值 中它默认为true(即上文文档描述的 LIFO 行为);若显式关闭该开关,请求拦截器改为push(FIFO)。因此升级或配置 transitional 选项时,务必确认自己的拦截器依赖的是哪种顺序。
六、多个拦截器的链式语义
同一请求/响应可以注册多个拦截器,它们在一条链上按如下规则流转:
- 每个拦截器接收前一个拦截器的返回值,只有最后一个拦截器的结果会被返回给请求方;
- 请求拦截器逆序(LIFO)执行,响应拦截器正序(FIFO)执行;
- 当某个
fulfilled处理器抛出异常时:- 下一个
fulfilled处理器不再被调用; - 紧接着调用下一个拦截器的
rejected处理器(而非同级的 rejected); - 一旦错误被捕获(
rejected处理器正常返回),链继续按 Promise 链的常规语义向后流转——下一个fulfilled重新参与调用。
- 下一个
这套语义与原生 Promise 的.then(onFulfilled, onRejected)链完全一致:axios 在异步模式下正是把每个拦截器展开为[fulfilled, rejected, fulfilled, rejected, ...]的扁平数组,然后循环promise = promise.then(chain[i++], chain[i++])(Axios.js)。理解这一点,你就能准确预测复杂拦截链中错误到底会被哪个处理器接住。
七、小结与延伸阅读
- 注册:
use(fulfilled, rejected, options),返回值是可用于eject的 ID; - 移除:
eject(id)精确移除,clear()全量清空; - 性能敏感且拦截器为同步代码时,用
{ synchronous: true }走同步分支(需全部请求拦截器都声明才生效),并理解同步分支下"错误被处理后照发请求"的规则; - 条件执行用
{ runWhen(config) },返回false即跳过; - 顺序:请求 LIFO、响应 FIFO,受
transitional.legacyInterceptorReqResOrdering(默认true)影响。
进一步阅读与验证依据:
- 拦截器管理器的完整实现:InterceptorManager.js
- 拦截链的构建与同步/异步分支:Axios.js
_request - transitional 默认值(含
legacyInterceptorReqResOrdering):transitional.js - 拦截器管理器的回归测试(墓碑、ID 失效、迭代中 eject/clear 等):InterceptorManager.test.js
- 相关文档:请求配置、错误处理、创建实例
【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考