Axios拦截器实现:Promise链式调用与请求响应处理
<|begin▁of▁sentence|># 1. 前言
在上一篇文章中,我们实现了`axios`的基础功能,但到目前为止,我们都只是实现了一个很简单的`axios`,一个完整的`axios`应该还有拦截器、配置化、取消请求等功能,那么从本篇文章开始,我们就来把这些功能一一实现。本篇文章先来实现拦截器功能。
# 2. 示例
我们先来看一下`axios`官方提供的拦截器使用示例:
```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`,它们都有一个`use`方法,`use`方法支持两个参数,第一个参数类似`Promise`的`resolve`函数,第二个参数类似`Promise`的`reject`函数。我们可以在`resolve`函数和`reject`函数中执行同步代码或者是异步代码逻辑。
并且,我们是可以添加多个拦截器的,拦截器的执行顺序是:对于请求拦截器,后添加的拦截器会在请求前的过程中先执行;对于响应拦截器,先添加的拦截器会在响应后先执行。
```javascript
// 添加请求拦截器1
axios.interceptors.request.use(
function(config) {
console.log("请求拦截器1");
return config;
},
function(error) {
return Promise.reject(error);
}
);
// 添加请求拦截器2
axios.interceptors.request.use(
function(config) {
console.log("请求拦截器2");
return config;
},
function(error) {
return Promise.reject(error);
}
);
// 添加响应拦截器1
axios.interceptors.response.use(
function(response) {
console.log("响应拦截器1");
return response;
},
function(error) {
return Promise.reject(error);
}
);
// 添加响应拦截器2
axios.interceptors.response.use(
function(response) {
console.log("响应拦截器2");
return response;
},
function(error) {
return Promise.reject(error);
}
);
```
在上述代码中,我们添加了两个请求拦截器和两个响应拦截器,那么当我们发起请求时,控制台的输出顺序应该是:
```javascript
请求拦截器2;
请求拦截器1;
响应拦截器1;
响应拦截器2;
```
OK,我们已经知道拦截器的使用了,接下来,我们就来一步一步实现它。
# 3. 实现思路
我们仔细思考一下:拦截器的本质是一个实现特定条件的`Promise`链式调用。请求拦截器,相当于在`Promise`链式调用的`dispatchRequest`之前添加;响应拦截器,相当于在`Promise`链式调用的`dispatchRequest`之后添加。如下:
```javascript
// 伪代码
// 添加拦截器前
Promise.resolve(config)
.then(dispatchRequest)
.then((response) => {
console.log(response);
});
// 添加请求拦截器和响应拦截器后
let newPromise = Promise.resolve(config);
// 遍历请求拦截器,插入到newPromise之后
interceptors.request.forEach((interceptor) => {
newPromise = newPromise.then(interceptor.resolve, interceptor.reject);
});
newPromise = newPromise.then(dispatchRequest);
// 遍历响应拦截器,插入到newPromise之后
interceptors.response.forEach((interceptor) => {
newPromise = newPromise.then(interceptor.resolve, interceptor.reject);
});
newPromise.then((response) => {
console.log(response);
});
```
所以,我们实现的思路应该是:
1. 定义一个类`InterceptorManager`用于管理拦截器,该类上有一个`use`实例方法用于添加拦截器,并且该类还应有一个存储拦截器的数组;
2. 在`Axios`类上有一个`interceptors`属性,该属性又有两个属性`request`和`response`,它们都是`InterceptorManager`的实例;
3. 在发送请求时,先获取所有拦截器,然后按照顺序插入到`Promise`链式调用中;
# 4. 拦截器管理类
根据实现思路,我们先来定义一个拦截器管理类`InterceptorManager`,我们在`src`目录下创建`interceptorManager.ts`文件:
```typescript
// src/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;
}
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;
}
}
}
```
我们定义了一个`InterceptorManager`泛型类,内部维护了一个私有属性`interceptors`,它是一个数组,用来存储拦截器。该类还对外提供了三个方法:
- `use`:添加一个拦截器到拦截器数组中,并返回一个`id`用于删除;
- `forEach`:遍历拦截器数组;
- `eject`:删除一个拦截器;
另外,我们给该类的泛型接口传入`T`,这是因为请求拦截器和响应拦截器添加的拦截函数参数类型不同,请求拦截器是`AxiosRequestConfig`类型,而响应拦截器是`AxiosResponse`类型。
# 5. 修改 Axios 类
定义好拦截器管理类之后,我们需要在`Axios`类上添加`interceptors`属性,并且该属性又有两个属性`request`和`response`,它们都是`InterceptorManager`的实例。并且,我们还需要修改`request`方法,在发送请求前先获取所有拦截器,然后按照顺序插入到`Promise`链式调用中。
## 5.1 添加 interceptors 属性
我们先在`Axios`类上添加`interceptors`属性:
```typescript
// src/core/Axios.ts
import { InterceptorManager } from "./InterceptorManager";
export class Axios {
public interceptors: {
request: InterceptorManager;
response: InterceptorManager;
};
constructor() {
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
}
```
## 5.2 修改 request 方法
然后,我们修改`request`方法,在发送请求前先获取所有拦截器,然后按照顺序插入到`Promise`链式调用中:
```typescript
// src/core/Axios.ts
import { InterceptorManager } from "./InterceptorManager";
export class Axios {
public interceptors: {
request: InterceptorManager;
response: InterceptorManager;
};
constructor() {
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
request(url: any, config?: any): AxiosPromise {
if (typeof url === "string") {
if (!config) {
config = {};
}
config.url = url;
} else {
config = url;
}
const chain: Array<
| {
resolved: ResolvedFn | ((config: AxiosRequestConfig) => AxiosPromise);
rejected?: RejectedFn;
}
| undefined
> = [
{
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;
}
}
```
代码说明:
- 首先,我们创建了一个数组`chain`,并把`dispatchRequest`函数赋值给`resolved`属性;
- 然后,先遍历请求拦截器插入到`chain`的前面;
- 接着,再遍历响应拦截器插入到`chain`的后面;
- 接下来,我们定义一个已经 resolve 的`promise`,循环这个`chain`,拿到每个拦截器对象,把它们的`resolved`函数和`rejected`函数添加到`promise.then`的参数中,这样就相当于通过`Promise`的链式调用方式,实现了拦截器一层层的链式调用的效果;
- 最后,返回`promise`;
需要注意的是:**请求拦截器是先添加的后执行,响应拦截器是先添加的先执行**。
# 6. 编写 demo
OK,拦截器功能我们已经实现完毕了,接下来,我们就编写`demo`来测试下效果如何。
我们在 `examples` 目录下创建 `interceptors`目录,在 `interceptors`目录下创建 `index.`:
```
< lang="en">
interceptors demo
>
```
接着再创建 `app.ts` 作为入口文件:
```typescript
// interceptors/app.ts
import axios from "../../src/index";
// 添加请求拦截器1
axios.interceptors.request.use(
(config) => {
config.headers.test += "requestInterceptor1---";
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 添加请求拦截器2
axios.interceptors.request.use(
(config) => {
config.headers.test += "requestInterceptor2---";
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 添加响应拦截器1
axios.interceptors.response.use(
(res) => {
res.data += "responseInterceptor1---";
return res;
},
(error) => {
return Promise.reject(error);
}
);
// 添加响应拦截器2
axios.interceptors.response.use(
(res) => {
res.data += "responseInterceptor2---";
return res;
},
(error) => {
return Promise.reject(error);
}
);
axios({
url: "/interceptor/get",
method: "get",
headers: {
test: "",
},
}).then((res) => {
console.log(res);
});
```
接着在 `server.js` 添加新的接口路由:
```javascript
// 注册interceptor接口
router.get("/interceptor/get", function(req, res) {
res.end("hello interceptor");
});
```
最后在根目录下的`index.`中加上启动该`demo`的入口:
```
interceptors
```
OK,我们在命令行中执行:
```bash
# 同时开启客户端和服务端
npm run server | npm start
```
接着浏览器打开 `http://localhost:8000/` 即可访问我们的 `demo` 了,我们点击 `interceptors`,通过`F12`的 `network` 部分我们可以看到请求已正常发出,并且我们可以观察请求的`headers`部分和响应返回的`data`部分,如下:
请求`headers`部分:

响应`data`部分:

从图中我们可以看到:请求头的`test`字段先是经过请求拦截器 1 的处理,添加了`requestInterceptor1---`,然后又经过请求拦截器 2 的处理,在之前的基础上又添加了`requestInterceptor2---`;而响应返回的`data`字段先是经过响应拦截器 1 的处理,添加了`responseInterceptor1---`,然后又经过响应拦截器 2 的处理,在之前的基础上又添加了`responseInterceptor2---`。
这完全符合我们之前所想的:请求拦截器是先添加的后执行,响应拦截器是先添加的先执行。
# 7. 遗留问题
我们虽然已经实现了拦截器功能,但是目前还存在一个问题:如果我们添加了多个请求拦截器,其中某个拦截器修改了`data`字段,而后续的拦截器并没有获取到修改后的`data`字段,例如:
```typescript
// 添加请求拦截器1
axios.interceptors.request.use(
(config) => {
config.data = {
a: 1,
};
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 添加请求拦截器2
axios.interceptors.request.use(
(config) => {
console.log(config.data);
return config;
},
(error) => {
return Promise.reject(error);
}
);
```
在请求拦截器 2 中打印`config.data`,我们期望的是打印出`{a:1}`,但是实际上打印出的是`undefined`,这是为什么呢?
这是因为:当我们第一个拦截器修改了`config`中的`data`后,`config`已经是一个普通对象了,而不再是`AxiosRequestConfig`类型了,所以我们在第二个拦截器中打印`config.data`时,`config`中已经没有`data`这个属性了。
这其实是一个`bug`,但是目前我们并不打算修复它,因为`axios`官方也存在这个`bug`,如下:

关于这个`bug`的修复,我们会在后续的文章中专门讲解。
# 8. 总结
本篇文章我们实现了`axios`的拦截器功能,拦截器的本质是一个实现特定条件的`Promise`链式调用。我们定义了一个拦截器管理类`InterceptorManager`,用于管理拦截器,然后在`Axios`类上添加了`interceptors`属性,该属性又有两个属性`request`和`response`,它们都是`InterceptorManager`的实例。最后,我们修改了`request`方法,在发送请求前先获取所有拦截器,然后按照顺序插入到`Promise`链式调用中。
另外,我们还遗留了一个问题:多个请求拦截器中,如果某个拦截器修改了`data`字段,后续拦截器并没有获取到修改后的值,关于这个问题,我们会在后续文章中专门讲解。
最后,附上本节课源码地址:[https://github.com/wangtao0101/axios/tree/2020-05-13](https://github.com/wangtao0101/axios/tree/2020-05-13)
最新文章
- 发动机轰鸣汽车飞驰在路上
- 汽车散热器冷却系统优化设计
- 未来汽车科技革命:智能座舱、固态电池与车路协同重塑出行
- 涡轮增压技术解析:原理、性能对比与未来趋势
- 二手车验车全攻略:轮胎磨损、底盘剐蹭、排气管检查要点
- 特斯拉电动跑车加速迅猛续航持久
- 平稳驾驶延长汽车寿命省油又安全
- V2X通信与智能传感器如何重塑未来出行体验
- 固态电池与高压快充引领智能座舱新浪潮
- 充电桩与三元锂电池:高能量密度时代的协同发展
- 汽车与未来出行新趋势
- 广汽传祺引领智能出行新时代
- 汽车日常保养指南:发动机与刹车系统维护要点
- 瑞驰汽车驾驶体验分享
- 激光雷达与毫米波雷达:自动驾驶技术的核心突破与应用前景
- 轮胎磨损影响汽车行驶安全定期检查
- 掌握发动机转速与空气滤清器保养,OBD行车电脑助你省油20%
- MaaS固态电池驱动未来汽车
- 汽车三大核心系统解析:发动机、变速箱与底盘
- 固态电池革命:能量密度突破与快充技术重塑汽车未来