Axios拦截器原理与执行顺序解析
<|begin▁of▁sentence|># 1. 前言
在之前的文章中,我们介绍了`axios`的拦截器,分别介绍了请求拦截器与响应拦截器,以及它们是如何注册以及调用的。并且还介绍了拦截器的执行顺序,其中有一个比较重要的点就是拦截器的执行顺序是:**先注册的请求拦截器后执行,先注册的响应拦截器先执行**。如下图所示:

那么,拦截器内部是如何实现以上执行顺序的呢?接下来,我们就从源码的角度来研究一下拦截器的内部实现原理。
# 2. 拦截器使用示例
我们先来看一下拦截器的使用示例:
```javascript
// 添加请求拦截器
axios.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
axios.interceptors.response.use(
function (response) {
// 对响应数据做点什么
return response;
},
function (error) {
// 对响应错误做点什么
return Promise.reject(error);
}
);
```
从上面的示例中我们可以看到:
- `axios` 对象上有 `interceptors` 属性,该属性又有 `request` 和 `response` 2 个属性,它们都有一个 `use` 方法;
- `use` 方法支持 2 个参数,第一个参数类似 `Promise` 的 `resolve` 函数,第二个参数类似 `Promise` 的 `reject` 函数;
- 我们可以在 `resolve` 函数和 `reject` 函数中执行同步代码或者是异步代码逻辑;
并且我们注意到,对于 `resolve` 函数的参数:
- 请求拦截器 `resolve` 函数参数是 `config` 对象,这个 `config` 对象就是接下来要发送请求的配置对象;
- 响应拦截器 `resolve` 函数参数是 `response` 对象,这个 `response` 对象是请求回来的响应对象;
对于 `reject` 函数的参数则是一个 `error` 对象。
# 3. 拦截器源码分析
## 3.1 拦截器管理类
通过使用示例我们已经知道:`axios` 对象上有 `interceptors` 属性,该属性又有 `request` 和 `response` 2 个属性,并且它们都有一个 `use` 方法。那么,这个 `interceptors` 属性是从哪里来的呢?通过之前对 `axios` 源码的分析,我们知道 `axios` 对象是通过 `createInstance` 函数创建出来的,在该函数内部有这样一段代码:
```javascript
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
utils.extend(instance, context);
return instance;
}
```
在 `createInstance` 函数内部,首先创建了 `Axios` 实例 `context`,接着创建了 `instance` 函数,该函数是 `Axios.prototype.request` 函数,并且函数的 `this` 指针指向 `context`;然后通过 `extend` 函数将 `Axios.prototype` 上的函数扩展到 `instance` 上,并且函数的 `this` 指针指向 `context`;最后再通过 `extend` 函数将 `context` 上的自身属性扩展到 `instance` 上。
所以,`instance` 上就有了 `context` 上的属性,而在 `Axios` 构造函数中,就有这么一段代码:
```javascript
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
```
可以看到,在 `Axios` 构造函数内部,在实例上添加了 `interceptors` 属性,该属性又有 `request` 和 `response` 2 个属性,并且它们都是 `InterceptorManager` 类的实例。
所以,接下来我们就来看一下 `InterceptorManager` 拦截器管理类。
## 3.2 InterceptorManager 构造函数
`InterceptorManager` 拦截器管理类的源码位于 `axios/lib/core/InterceptorManager.js`中,代码如下:
```javascript
function InterceptorManager() {
this.handlers = [];
}
```
可以看到,`InterceptorManager` 构造函数非常简单,在其内部初始化了一个 `handlers` 空数组,用来存储拦截器。
## 3.3 use 方法
`InterceptorManager` 的原型上有一个 `use` 方法,该方法就是我们使用拦截器的时候调用的方法,代码如下:
```javascript
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
```
`use` 方法接收两个参数,分别是 `resolve` 函数和 `reject` 函数;然后向 `handlers` 数组中添加一个对象,该对象有 `fulfilled` 和 `rejected` 两个属性,分别对应传入的 `resolve` 函数和 `reject` 函数;最后返回当前拦截器在 `handlers` 数组中的索引,以便后面可以通过这个索引来移除拦截器。
## 3.4 eject 方法
`InterceptorManager` 的原型上还有一个 `eject` 方法,该方法用来移除拦截器,代码如下:
```javascript
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
```
`eject` 方法接收一个参数 `id`,该参数是拦截器在 `handlers` 数组中的索引,然后通过这个索引将 `handlers` 数组中对应的拦截器设置为 `null`。这里没有使用 `splice` 方法将拦截器从 `handlers` 中删除,而是将其设置为 `null`,是因为如果我们删除了该拦截器,那么 `handlers` 数组的长度就会改变,那么之前记录的索引就会发生变化,所以这里只是将其设置为 `null`,在遍历的时候跳过即可。
## 3.5 forEach 方法
`InterceptorManager` 的原型上还有一个 `forEach` 方法,该方法用来遍历 `handlers` 数组中的拦截器,代码如下:
```javascript
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
```
`forEach` 方法接收一个参数 `fn`,该参数是一个函数,然后在 `handlers` 数组中遍历每一个拦截器,如果拦截器不为 `null`,就调用 `fn` 函数,并将拦截器作为参数传入。
OK,以上就是 `InterceptorManager` 拦截器管理类的所有代码,非常简单,就是用来管理拦截器的,包括添加拦截器、移除拦截器、遍历拦截器。
# 4. 拦截器执行顺序
在之前的文章中,我们介绍过拦截器的执行顺序是:**先注册的请求拦截器后执行,先注册的响应拦截器先执行**。那么,这个顺序是如何实现的呢?接下来,我们就从源码的角度来分析一下。
我们知道,`axios` 的核心是 `Axios.prototype.request` 函数,而拦截器的执行就是在该函数中实现的。所以,我们来看一下 `Axios.prototype.request` 函数的源码,该函数位于 `axios/lib/core/Axios.js` 中。
由于该函数代码较长,我们只截取与拦截器相关的部分:
```javascript
Axios.prototype.request = function request(config) {
// ...
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
```
首先,定义了一个数组 `chain`,该数组初始值为 `[dispatchRequest, undefined]`,其中 `dispatchRequest` 就是发送请求的函数,`undefined` 是为了占位,因为 `then` 方法接收两个参数,分别是 `resolve` 函数和 `reject` 函数,所以这里用 `undefined` 占位。
然后,创建了一个 `promise` 对象,该 `promise` 对象的状态是 `resolved`,值是 `config`。
接着,遍历请求拦截器,将每一个请求拦截器的 `resolve` 函数和 `reject` 函数通过 `unshift` 方法添加到 `chain` 数组的头部。所以,**先注册的请求拦截器会被添加到 `chain` 数组的头部,后注册的请求拦截器会被添加到 `chain` 数组的头部的前面**。
然后,遍历响应拦截器,将每一个响应拦截器的 `resolve` 函数和 `reject` 函数通过 `push` 方法添加到 `chain` 数组的尾部。所以,**先注册的响应拦截器会被添加到 `chain` 数组的尾部,后注册的响应拦截器会被添加到 `chain` 数组的尾部的后面**。
最后,通过 `while` 循环,每次从 `chain` 数组中取出两个元素,第一个是 `resolve` 函数,第二个是 `reject` 函数,然后通过 `promise.then` 方法依次调用。因为 `promise` 初始状态是 `resolved`,所以会先执行请求拦截器的 `resolve` 函数,然后执行 `dispatchRequest` 函数发送请求,最后执行响应拦截器的 `resolve` 函数。
所以,整个拦截器的执行顺序就是:
1. 先注册的请求拦截器后执行,后注册的请求拦截器先执行;
2. 然后执行 `dispatchRequest` 函数发送请求;
3. 最后先注册的响应拦截器先执行,后注册的响应拦截器后执行。
# 5. 总结
本篇文章我们主要分析了 `axios` 拦截器的实现原理。首先,我们介绍了拦截器的使用示例;然后,我们分析了拦截器管理类 `InterceptorManager` 的源码,包括 `use` 方法、`eject` 方法和 `forEach` 方法;最后,我们分析了拦截器的执行顺序,以及是如何实现的。
通过分析,我们知道了拦截器的执行顺序是:**先注册的请求拦截器后执行,先注册的响应拦截器先执行**。这个顺序是通过将请求拦截器添加到 `chain` 数组的头部,将响应拦截器添加到 `chain` 数组的尾部,然后通过 `promise.then` 方法依次调用实现的。
最新文章
- 充电桩普及推动智能后视镜技术革新
- 双向充电技术:让电动汽车变身移动储能站的未来革命
- 机油更换周期影响汽车发动机性能
- 固态电池革命:突破续航瓶颈,重塑新能源汽车未来
- 智能座舱革命:从人车交互到移动生活空间的进化
- 智能油门控制技术:基于车载传感器与V2X的节油驾驶方案
- 汽车刹车片安全驾驶关键
- 汽车照明效果优化方案
- 发动机过热冷却系统故障需检查冷却液和风扇
- 汽车传动系统优化技术
- 新能源汽车续航提升电池技术革新固态电池突破电机效率优化轻量化设计空气动力学改进能量回收系统升级锂离子电池电机散热器创新
- 电动化与智能驾驶双轮驱动:车联网重塑未来出行新生态
- 固态电池与高压快充引领智能座舱新浪潮
- 未来汽车三大科技革命:自动驾驶、固态电池与智能座舱
- 智能驾驶与车联网技术重塑未来出行新生态
- 智能驾驶与车联网技术加速演进,800V高压平台成新趋势
- 汽车与未来交通发展
- 机动车行驶证办理全攻略:流程、材料及常见问题解答
- 智能驾驶技术突破:从L3到L4的五年变革之路
- 智能座舱与电动化引领未来车联网新趋势
