Axios拦截器实现原理与功能详解
<|begin▁of▁sentence|># 1. 前言
在之前的文章中,我们介绍了`axios`的拦截器,并简单实现了其功能。拦截器包括请求拦截器和响应拦截器,可以在请求发送前和响应返回后对数据进行处理。这是`axios`非常强大的一点,接下来,我们就来探究一下`axios`拦截器的设计思想,并编写代码实现。
# 2. 拦截器的使用
在介绍拦截器的设计思想之前,我们先来看下拦截器是如何使用的。
## 2.1 添加拦截器
```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);
}
);
```
## 2.2 移除拦截器
```javascript
const myInterceptor = axios.interceptors.request.use(function() {
/*...*/
});
axios.interceptors.request.eject(myInterceptor);
```
## 2.3 为自定义 axios 实例添加拦截器
```javascript
const instance = axios.create();
instance.interceptors.request.use(function() {
/*...*/
});
```
# 3. 拦截器的设计思想
通过上面的使用示例,我们可以看到:
- `axios`对象上有一个`interceptors`对象属性,该属性又有`request`和`response`2 个属性,它们都有一个`use`方法和`eject`方法。`use`方法用于添加拦截器函数,`eject`方法用于移除拦截器函数。
- 当我们使用`use`方法添加拦截器函数时,该方法会返回一个拦截器`id`,以便在需要的时候通过`eject`方法将拦截器移除。
- 另外,我们还可以为自定义的`axios`实例添加拦截器。
那么,根据以上使用方式,我们可以提炼出以下要点:
1. `axios`对象上有`interceptors`属性,且该属性有`request`和`response`属性;
2. `interceptors.request`和`interceptors.response`对象上有`use`和`eject`方法;
3. `use`方法接收两个参数,分别是处理成功和处理失败的函数,并且返回一个拦截器`id`;
4. `eject`方法接收拦截器`id`作为参数,用来移除拦截器;
5. 可以为自定义`axios`实例添加拦截器;
那么,接下来,我们就根据以上要点来实现拦截器。
# 4. 拦截器的实现
## 4.1 实现思路
根据上面的分析,我们可以在`Axios`构造函数上添加一个`interceptors`属性,该属性又有`request`和`response`两个属性,这两个属性都是`InterceptorManager`类的实例,该类是用来管理拦截器的,它内部会有`use`和`eject`方法,分别用来添加和移除拦截器。
另外,当通过`use`方法添加拦截器时,每一个拦截器函数都会被保存到该类实例的`handlers`数组中,并且每一个拦截器函数都会有一个`id`,该`id`就是当前拦截器在`handlers`数组中的索引下标,同时`use`方法会返回该`id`,以便后续通过`eject`方法将拦截器移除。
OK,思路已经理清,接下来,我们就开始编写代码实现。
## 4.2 代码实现
### 4.2.1 创建 InterceptorManager 类
我们先创建一个`InterceptorManager`类,该类是用来管理拦截器的,代码如下:
```typescript
// src/core/InterceptorManager.ts
import { ResolvedFn, RejectedFn } from "../types";
interface Interceptor {
resolved: ResolvedFn;
rejected?: RejectedFn;
}
export default class InterceptorManager {
private interceptors: Array | null>;
constructor() {
this.interceptors = [];
}
use(resolved: ResolvedFn, rejected?: RejectedFn): number {
this.interceptors.push({
resolved,
rejected,
});
return this.interceptors.length - 1;
}
eject(id: number): void {
if (this.interceptors[id]) {
this.interceptors[id] = null;
}
}
}
```
我们创建了一个`InterceptorManager`类,该类被定义为一个泛型类,因为我们即需要管理请求拦截器又需要管理响应拦截器,而请求拦截器和响应拦截器的参数类型是不同的,所以我们需要使用泛型参数`T`。
在该类的内部,我们定义了一个私有属性`interceptors`,它是一个数组,用来存储拦截器,数组的每一项是一个`Interceptor`类型对象,该对象包含`resolved`和`rejected`属性。
接着,我们给该类添加了`use`方法,该方法接收两个参数,分别是`resolved`函数和`rejected`函数,调用该方法会向`interceptors`数组中`push`一个`Interceptor`对象,并返回该对象在数组中的索引,也就是拦截器`id`。
最后,我们给该类添加了`eject`方法,该方法接收拦截器`id`作为参数,将`interceptors`数组中对应`id`的项置为`null`。这里我们并没有将该拦截器从数组中删除,而是置为`null`,这样做是为了避免我们在遍历`interceptors`数组时,删除某项后数组的索引发生变化,导致`eject`的`id`不是之前`use`返回的`id`。
### 4.2.2 修改 Axios 类
创建好`InterceptorManager`类之后,我们需要在`Axios`类中创建`interceptors`属性,如下:
```typescript
// src/core/Axios.ts
import { InterceptorManager } from "./InterceptorManager";
interface Interceptors {
request: InterceptorManager;
response: InterceptorManager;
}
export default class Axios {
interceptors: Interceptors;
constructor() {
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
}
```
我们在`Axios`类中创建了`interceptors`属性,该属性是一个对象,包含`request`和`response`属性,它们分别是`InterceptorManager`类的实例,并且`InterceptorManager`类的泛型参数分别为`AxiosRequestConfig`和`AxiosResponse`。
OK,这样,我们就完成了拦截器的添加和移除功能。但是,光有添加和移除还不行,我们还需要在发送请求的时候遍历所有添加的拦截器,并依次执行。
### 4.2.3 遍历拦截器
我们在发送请求的时候,需要先遍历请求拦截器,然后发送请求,最后遍历响应拦截器。那么,我们该如何遍历呢?
我们可以在`Axios`类的`request`方法中,先定义一个数组,该数组中保存了所有要执行的函数,包括请求拦截器、发送请求、响应拦截器。然后,我们依次执行该数组中的函数。如下:
```javascript
const chain = [
{
resolved: dispatchRequest,
rejected: undefined,
},
];
this.interceptors.request.forEach((interceptor) => {
chain.unshift(interceptor);
});
this.interceptors.response.forEach((interceptor) => {
chain.push(interceptor);
});
```
我们先把发送请求的函数`dispatchRequest`放在`chain`数组的中间,然后遍历请求拦截器,将每个请求拦截器从`chain`数组的前面插入;接着遍历响应拦截器,将每个响应拦截器从`chain`数组的后面插入。这样,当我们依次执行`chain`数组中的函数时,就可以保证先执行请求拦截器,然后发送请求,最后执行响应拦截器。
但是,我们该如何执行`chain`数组中的函数呢?我们可以使用`Promise`来链式调用,如下:
```javascript
let promise = Promise.resolve(config);
while (chain.length) {
const { resolved, rejected } = chain.shift()!;
promise = promise.then(resolved, rejected);
}
return promise;
```
我们先创建一个已经`resolve`的`promise`,然后循环`chain`数组,将数组中的每一项的`resolved`函数和`rejected`函数添加到`promise.then`的参数中,这样就相当于通过`Promise`的链式调用方式依次执行`chain`数组中的函数。
OK,思路已经理清,接下来,我们就按照该思路修改`Axios`类的`request`方法。
### 4.2.4 修改 request 方法
我们修改`Axios`类的`request`方法,如下:
```typescript
// src/core/Axios.ts
request(url: any, config?: any): AxiosPromise {
if (typeof url === 'string') {
if (!config) {
config = {};
}
config.url = url;
} else {
config = url;
}
const chain: Array> = [
{
resolved: dispatchRequest,
rejected: undefined
}
];
this.interceptors.request.forEach(interceptor => {
chain.unshift(interceptor);
});
this.interceptors.response.forEach(interceptor => {
chain.push(interceptor);
});
let promise = Promise.resolve(config);
while (chain.length) {
const { resolved, rejected } = chain.shift()!;
promise = promise.then(resolved, rejected);
}
return promise;
}
```
在上面的代码中,我们先判断了第一个参数`url`是否为字符串类型,如果是的话,说明用户第一个参数传的是`url`,第二个参数是`config`,那么我们将`url`添加到`config`中;如果不是字符串类型,说明用户第一个参数直接传的就是`config`,那么我们将`url`赋值给`config`。
接着,我们定义了`chain`数组,该数组中默认存放发送请求的函数`dispatchRequest`。
然后,我们遍历所有的请求拦截器,将每个请求拦截器从`chain`数组的前面插入;接着遍历所有的响应拦截器,将每个响应拦截器从`chain`数组的后面插入。
最后,我们创建了一个已经`resolve`的`promise`,循环`chain`数组,将数组中的每一项的`resolved`函数和`rejected`函数添加到`promise.then`的参数中,通过`Promise`的链式调用方式依次执行`chain`数组中的函数。
另外,我们还需要给`InterceptorManager`类添加`forEach`方法,用来遍历`interceptors`数组中的拦截器。
### 4.2.5 实现 forEach 方法
我们在`InterceptorManager`类中添加`forEach`方法,如下:
```typescript
// src/core/InterceptorManager.ts
export default class InterceptorManager {
private interceptors: Array | null>;
constructor() {
this.interceptors = [];
}
use(resolved: ResolvedFn, rejected?: RejectedFn): number {
this.interceptors.push({
resolved,
rejected,
});
return this.interceptors.length - 1;
}
forEach(fn: (interceptor: Interceptor) => void): void {
this.interceptors.forEach((interceptor) => {
if (interceptor !== null) {
fn(interceptor);
}
});
}
eject(id: number): void {
if (this.interceptors[id]) {
this.interceptors[id] = null;
}
}
}
```
在`forEach`方法中,我们遍历`interceptors`数组,并将数组中的每一项作为参数传给传入的`fn`函数,但是需要注意的是,如果数组中的某一项为`null`,那么我们就不遍历它。
OK,至此,我们就实现了拦截器的添加、移除和遍历功能。
# 5. 编写 demo
接下来,我们编写一个 `demo` 来测试下我们实现的拦截器功能是否正常。
```javascript
// examples/interceptor/app.ts
import axios from "../../src/index";
axios.interceptors.request.use((config) => {
config.headers.test += "1";
return config;
});
axios.interceptors.request.use((config) => {
config.headers.test += "2";
return config;
});
axios.interceptors.request.use((config) => {
config.headers.test += "3";
return config;
});
axios.interceptors.response.use((res) => {
res.data += "1";
return res;
});
let interceptor = axios.interceptors.response.use((res) => {
res.data += "2";
return res;
});
axios.interceptors.response.use((res) => {
res.data += "3";
return res;
});
axios.interceptors.response.eject(interceptor);
axios({
url: "/interceptor/get",
method: "get",
headers: {
test: "",
},
}).then((res) => {
console.log(res.data);
});
```
在该 `demo` 中,我们添加了 3 个请求拦截器,每个拦截器都在请求头 `headers` 的 `test` 字段上追加了一个数字,所以最终 `test` 字段的值是 `123`。
然后,我们又添加了 3 个响应拦截器,每个拦截器都在响应数据的后面追加了一个数字,所以最终响应数据后面会追加 `123`。但是,我们在添加完第二个响应拦截器的时候,保存了该拦截器的 `id`,然后又移除了该拦截器,所以最终响应数据后面追加的是 `13`。
我们在命令行中执行:
```bash
# 同时开启客户端和服务端
npm run server | npm start
```
然后我们打开浏览器,访问:`http://localhost:8000/`,然后点击 `interceptor`,通过`F12`的 `network` 可以看到我们发送的请求,并且请求头中 `test` 字段的值为 `123`,并且响应数据为 `{"message":"hello world"}13`,如下:


OK,测试结果正常。
# 6. 遗留问题
我们虽然已经实现了拦截器的基本功能,但是仍然存在一些问题,例如:如果我们想在请求拦截器中做一些异步操作,那么我们的代码就无法正常工作。因为我们在遍历`chain`数组的时候,是同步执行的,而异步操作是无法通过同步方式获取到结果的。
不过,这并不影响我们理解拦截器的实现原理,而且`axios`官方也是这么实现的,所以我们暂时先不考虑异步的情况,后续如果有需要,我们再对其进行优化。
# 7. 总结
本篇文章中,我们介绍了拦截器的使用方式,分析了拦截器的设计思想,并实现了拦截器的添加、移除和遍历功能。
拦截器的实现思路如下:
1. 创建一个`InterceptorManager`类,用来管理拦截器,该类内部有一个`interceptors`数组,用来存储拦截器,并且有`use`和`eject`方法,分别用来添加和移除拦截器;
2. 在`Axios`类中创建`interceptors`属性,该属性包含`request`和`response`两个属性,它们分别是`InterceptorManager`类的实例;
3. 在`Axios`类的`request`方法中,先定义一个数组`chain`,默认存放发送请求的函数`dispatchRequest`;
4. 然后遍历所有的请求拦截器,将每个请求拦截器从`chain`数组的前面插入;接着遍历所有的响应拦截器,将每个响应拦截器从`chain`数组的后面插入;
5. 最后,通过`Promise`的链式调用方式依次执行`chain`数组中的函数;
至此,`axios`的拦截器我们就实现完毕了。
最新文章
- 智能座舱与自动驾驶:车联网重塑未来出行新生态
- 高精地图:自动驾驶迈向L4时代的核心导航大脑
- 智能驾驶与新能源革命:未来出行的三大技术趋势
- 铝合金轻量化汽车车身设计
- 汽车与未来出行新趋势
- 电动化与智能化并进:电池技术、自动驾驶与车联网的融合发展
- V2X通信与智能传感器如何重塑未来出行体验
- 车路协同技术解析:车载单元与路侧单元如何重塑智能驾驶
- 动力电池与激光雷达引领新能源汽车智能化革命
- 宝马全新电动SUV续航里程突破600公里
- 电动汽车三大技术突破:固态电池、无线充电与高效回收
- 电动化与智能化双轮驱动:电池技术、激光雷达与车联网重塑未来出行
- 共享汽车如何破解找车难、停车贵、车况差三大痛点?
- 车载传感器实时监测路况信息
- 机动车行驶证办理全攻略:流程、材料及常见问题解答
- 智能驾驶算法优化汽车性能
- 单车事故导致汽车严重损毁
- 电动车续航焦虑解析:毫米波雷达如何助力智能驾驶安全
- 转向灯使用指南:安全驾驶与故障处理全解析
- 汽车色温选择指南:18-30字科学解析驾驶舒适度
