Axios拦截器实现全解析:从原理到实战应用
<|begin▁of▁sentence|># 1. 前言
在之前的文章中,我们介绍了`axios`的拦截器,并用示意图展示了拦截器在请求从发出到响应返回过程中的工作流程。我们知道,拦截器分为请求拦截器和响应拦截器两种。请求拦截器的作用是拦截请求,在请求发送之前做一些事情;响应拦截器的作用是拦截响应,在响应返回到`then/catch`之前做一些事情。从之前的示意图中我们可以看到,拦截器的执行过程是一个链式调用的方式,并且每个拦截器都可以设置两个回调函数,一个表示成功,一个表示失败。并且我们还可以随时在拦截器数组中添加新的拦截器,另外,我们也支持删除某个拦截器。
那么,接下来,我们就来自己实现一下`axios`的拦截器。
# 2. 需求分析
我们实现拦截器大致需要实现以下功能:
1. 拦截器分为请求拦截器和响应拦截器两种;
2. 每种拦截器都可以设置多个,并且每个拦截器都可以设置两个回调函数,一个用于成功,一个用于失败;
3. 每种拦截器都支持通过`use`方法添加;
4. 每种拦截器都支持通过`eject`方法删除;
5. 拦截器的执行过程是一个链式调用的方式;
# 3. 实现思路
## 3.1 定义拦截器管理类
首先,我们可以为拦截器定义一个管理类,该类里面可以提供`use`、`eject`等方法供外界使用,并且该类还需要维护一个数组,用来存储用户通过`use`方法添加的拦截器。
## 3.2 定义拦截器接口
其次,我们需要定义拦截器的接口,因为每个拦截器都可以设置两个回调函数,一个表示成功,一个表示失败,并且每个拦截器都还要有一个`id`,以便后续删除。
## 3.3 把拦截器挂载到axios实例上
然后,我们还需要把定义好的请求拦截器管理类和响应拦截器管理类挂载到`axios`实例上,并且起名为`interceptors`,它的结构如下:
```javascript
axios.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
```
## 3.4 修改request函数
最后,我们还需要修改`request`函数,在`request`函数内部来组织拦截器与真正请求的调度关系。
OK,以上就是我们实现拦截器的大致思路,接下来,我们就按照这个思路来一步步实现它。
# 4. 拦截器管理类实现
## 4.1 定义拦截器接口
我们创建一个`InterceptorManager`拦截器管理类,在该类中,我们定义一个数组`interceptors`用来存储用户添加的拦截器,并且每个拦截器我们规定它是一个对象,其中`fulfilled`表示成功时的回调,`rejected`表示失败时的回调,并且每个拦截器还要有一个`id`,以便后续删除。如下:
```typescript
export interface ResolvedFn {
(val: T): T | Promise
}
export interface RejectedFn {
(error: any): any
}
export interface Interceptor {
fulfilled: ResolvedFn
rejected?: RejectedFn
id: number
}
```
## 4.2 实现InterceptorManager类
我们为`InterceptorManager`类定义如下方法:
- `use`:添加拦截器,返回拦截器`id`,以便删除;
- `eject`:删除拦截器;
- `forEach`:遍历拦截器;
具体实现如下:
```typescript
export default class InterceptorManager {
// 拦截器数组
private interceptors: Array | null>
constructor() {
this.interceptors = []
}
// 添加拦截器
use(fulfilled: ResolvedFn, rejected?: RejectedFn): number {
this.interceptors.push({
fulfilled,
rejected,
id: this.interceptors.length
})
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
}
}
}
```
代码说明:
- 在`use`方法中,每添加一个拦截器,我们就为它创建一个对象,并分配一个`id`,然后将该对象`push`到`interceptors`中,并且返回这个`id`;
- 在`eject`方法中,我们通过`id`来查找对应的拦截器,找到后将其置为`null`,这里我们并没有使用`splice`等数组方法来删除,是为了避免删除后数组索引发生变化,导致`forEach`遍历时错乱;
- 在`forEach`方法中,我们遍历所有拦截器,并执行传入的回调,并且会跳过为`null`的拦截器;
OK,拦截器管理类就实现好了。
# 5. 把拦截器挂载到axios实例上
拦截器管理类实现好之后,我们需要把它挂载到`axios`实例上,并且起名为`interceptors`,它的结构如下:
```javascript
axios.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
```
首先,我们在`src/core/Axios.ts`中定义`interceptors`属性,如下:
```typescript
import { InterceptorManager } from "./InterceptorManager";
// 定义接口
export interface Interceptors {
request: InterceptorManager
response: InterceptorManager
}
// 在Axios类中添加interceptors属性
export default class Axios {
defaults: AxiosRequestConfig
interceptors: Interceptors
constructor(initConfig: AxiosRequestConfig) {
this.defaults = initConfig
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
}
// ...
}
```
然后,我们还需要在`src/core/InterceptorManager.ts`中把`InterceptorManager`类导出,如下:
```typescript
export * from "./InterceptorManager";
```
# 6. 修改request函数
拦截器挂载好之后,接下来我们就需要在`request`函数内部来组织拦截器与真正请求的调度关系了。
在之前的文章中,我们介绍拦截器工作流程的时候,画过一张图,如下:

从图中我们可以看到,请求拦截器是在请求之前执行,响应拦截器是在响应之后执行,并且整个过程是一个链式调用的方式。那么,我们该如何在`request`函数内部实现这个链式调用呢?
其实,我们可以把整个请求的调度看做是一个链条,这个链条上的每个节点都是一个Promise对象,而每个Promise对象都拥有两个方法:`then`和`catch`。那么,我们就可以把整个链条拆分成多个Promise对象,然后通过`then`方法把它们串联起来。
具体思路如下:
1. 首先,我们创建一个数组`chain`,该数组用来存放链条上的每个节点,每个节点都是一个Promise对象;
2. 然后,我们把请求拦截器中的每个拦截器都放到`chain`数组的前面,并且每个拦截器都有两个回调函数,一个表示成功,一个表示失败;
3. 接着,我们把真正的请求`dispatchRequest`放到`chain`数组中;
4. 最后,我们把响应拦截器中的每个拦截器都放到`chain`数组的后面,并且每个拦截器都有两个回调函数,一个表示成功,一个表示失败;
5. 然后,我们通过`Promise.resolve(config)`初始化一个Promise对象,然后通过`while`循环依次取出`chain`数组中的每个节点,然后通过`then`方法把它们串联起来;
6. 最后,返回这个Promise对象;
OK,思路清晰之后,接下来我们就按照这个思路来修改`request`函数,如下:
```typescript
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 => {
if (interceptor !== null) {
chain.unshift(interceptor)
}
})
// 响应拦截器:往数组后面压入
this.interceptors.response.forEach(interceptor => {
if (interceptor !== null) {
chain.push(interceptor)
}
})
// 初始化一个promise,resolve参数是config
let promise = Promise.resolve(config)
// 循环chain数组,取出每个拦截器,然后通过then串联起来
while (chain.length) {
const { resolved, rejected } = chain.shift()!
promise = promise.then(resolved, rejected)
}
return promise
}
```
代码说明:
- 首先,我们创建一个数组`chain`,并把真正的请求`dispatchRequest`函数包装成一个对象放到`chain`中;
- 然后,我们遍历请求拦截器,把每个请求拦截器都通过`unshift`方法放到`chain`数组的前面,注意:这里是从后往前遍历,所以先添加的请求拦截器会后执行;
- 接着,我们遍历响应拦截器,把每个响应拦截器都通过`push`方法放到`chain`数组的后面,注意:这里是从前往后遍历,所以先添加的响应拦截器会先执行;
- 然后,我们初始化一个`Promise`对象,`resolve`的参数是`config`;
- 接着,我们通过`while`循环依次取出`chain`数组中的每个节点,然后通过`then`方法把它们串联起来;
- 最后,返回这个`Promise`对象;
# 7. 编写demo
OK,拦截器实现好了之后,我们就编写一个`demo`来测试下效果如何。
我们在`examples`目录下创建`interceptor.`文件,代码如下:
```
< lang="en">
interceptor demo
>
```
然后,我们在`server/interceptor.js`中添加接口路由,如下:
```javascript
router.get("/api/interceptor/get", function(req, res) {
res.end("hello, interceptor");
});
```
接着,我们打开命令行,执行:
```bash
# 同时开启客户端和服务端
npm run server | npm start
```
接着,我们打开浏览器,访问:`http://localhost:8000/`,然后点击 `interceptor`,通过`F12`的 `Network` 部分我们可以看到请求已正常发出,并且打开浏览器控制台,我们可以看到打印出的日志如下:

从图中我们可以看到,请求拦截器按照添加的顺序从后往前执行,响应拦截器按照添加的顺序从前往后执行,并且整个过程是一个链式调用的方式。
我们再来测试下删除拦截器,在`demo`中,我们添加完拦截器之后,再删除某个拦截器,如下:
```javascript
// 添加请求拦截器1
const requestInterceptorId1 = axios.interceptors.request.use(function(config) {
console.log("request interceptor1 resolved");
return config;
});
// 添加请求拦截器2
const requestInterceptorId2 = axios.interceptors.request.use(function(config) {
console.log("request interceptor2 resolved");
return config;
});
// 删除请求拦截器2
axios.interceptors.request.eject(requestInterceptorId2);
// 添加响应拦截器1
const responseInterceptorId1 = axios.interceptors.response.use(function(response) {
console.log("response interceptor1 resolved");
return response;
});
// 添加响应拦截器2
const responseInterceptorId2 = axios.interceptors.response.use(function(response) {
console.log("response interceptor2 resolved");
return response;
});
// 删除响应拦截器1
axios.interceptors.response.eject(responseInterceptorId1);
```
然后,我们再次刷新页面,打开浏览器控制台,我们可以看到打印出的日志如下:

从图中我们可以看到,请求拦截器2和响应拦截器1都被删除了,并没有执行。
OK,以上就是拦截器的实现。
# 8. 遗留问题
虽然我们已经实现了拦截器的基本功能,但是还是有一些细节问题需要处理,例如:
1. 在`request`函数中,我们通过`Promise.resolve(config)`初始化了一个`Promise`对象,然后通过`then`方法串联起所有拦截器,但是如果在某个拦截器中抛出异常,那么这个异常会被后面的拦截器的`rejected`回调捕获,但是如果最后一个拦截器也抛出异常,那么这个异常就会变成`Uncaught (in promise)`,我们应该如何处理?
2. 在拦截器中,我们可以修改`config`或`response`,但是我们在`request`函数中并没有对修改后的`config`或`response`做任何处理,我们应该如何处理?
这些问题,我们会在后面的文章中一一解决。
# 9. 总结
本篇文章我们实现了`axios`的拦截器功能。我们首先分析了拦截器的需求,然后介绍了实现思路,接着我们按照思路一步步实现了拦截器管理类,并把拦截器挂载到`axios`实例上,最后我们修改了`request`函数,在`request`函数内部组织拦截器与真正请求的调度关系。实现完成后,我们还编写了`demo`测试了拦截器的效果。
最后,我们还遗留了两个问题,这些问题我们会在后面的文章中解决。
最新文章
- 风扇转动汽车引擎冷却系统高效运转
- 交通事故责任认定与汽车保险
- 智能温控风扇系统优化汽车能量管理
- 燃油车百年发展史:从机械革命到新能源转型
- 汽车与未来出行的革新之路
- 锂离子电池与V2X技术引领未来出行革命
- 《汽车钥匙没电怎么办?4个应急启动妙招全解析》
- 未来汽车三大黑科技:智能座舱、固态电池与车路协同
- 刹车踏板与汽车安全关系
- 智能驾驶革命:重塑未来出行的核心技术与发展趋势
- V2X车联网与激光雷达技术重塑智能出行新时代
- 智能传感器革命:重塑未来汽车感知与交互新生态
- 发动机气门正时优化指南:提升动力与燃油效率的关键
- 汽车散热系统高效冷却引擎确保稳定运行
- 计算机控制汽车智能驾驶技术
- 高强度钢材+盗抢险:双重守护爱车的安全屏障
- 防腐蚀涂层提升汽车耐用性延长使用寿命
- 车险省钱攻略:五大因素影响保费,比价技巧全解析
- 激光大灯技术革命:突破600米照射距离的智能照明未来
- 智能座舱革新+车路协同突破:2023汽车科技三大趋势解析
