Axios拦截器实现与异常处理全解析
<|begin▁of▁sentence|># 1. 前言
在之前的文章中,我们介绍了`axios`的拦截器,并用流程图说明了拦截器在`axios`整体请求与响应流程中起的作用,最后我们实现了拦截器管理类以及链式调用逻辑。今天我们就把拦截器运用到`axios`中去。
# 2. 修改 Axios 类
我们之前实现的`Axios`类中只有一个简单`request`方法,如下:
```typescript
request(url: any, config?: any): AxiosPromise {
if (typeof url === "string") {
if (!config) {
config = {};
}
config.url = url;
} else {
config = url;
}
return dispatchRequest(config);
}
```
接下来,我们要把之前写好的拦截器运用其中,首先,在`Axios`类中定义两个属性,分别为拦截器管理类的实例,如下:
```typescript
import { InterceptorManager } from "./InterceptorManager";
export class Axios {
defaults: AxiosRequestConfig;
interceptors: {
request: InterceptorManager;
response: InterceptorManager;
};
constructor(initConfig: AxiosRequestConfig) {
this.defaults = initConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
// ...
}
```
然后,我们还要修改`request`方法,把拦截器的链式调用逻辑添加到该方法中,如下:
```typescript
request(url: any, config?: any): AxiosPromise {
if (typeof url === "string") {
if (!config) {
config = {};
}
config.url = url;
} else {
config = url;
}
// 组成链条
const chain: PromiseChain[] = [
{
resolved: dispatchRequest,
rejected: undefined,
},
];
// 请求拦截器添加到链条前面
this.interceptors.request.forEach((interceptor) => {
chain.unshift(interceptor);
});
// 响应拦截器添加到链条后面
this.interceptors.response.forEach((interceptor) => {
chain.push(interceptor);
});
// 把config也包装成一个Promise
let promise = Promise.resolve(config);
// 遍历链条,依次执行
while (chain.length) {
const { resolved, rejected } = chain.shift()!;
promise = promise.then(resolved, rejected);
}
return promise;
}
```
OK,这样我们就把拦截器的链式调用逻辑添加到`request`方法中了。
# 3. 修改 dispatchRequest 函数
我们之前实现的`dispatchRequest`函数如下:
```typescript
function dispatchRequest(config: AxiosRequestConfig): AxiosPromise {
processConfig(config);
return xhr(config).then(
(res) => {
return transformResponseData(res);
},
(e) => {
return Promise.reject(e);
}
);
}
```
该函数内部调用了`xhr`函数,并且将`xhr`函数返回的`Promise`对象直接返回了,但是我们在之前介绍拦截器的时候说过,拦截器的执行顺序是:
请求拦截器 -> 分发请求 -> 响应拦截器
所以,`dispatchRequest`函数返回的应该是`xhr`函数返回的`Promise`对象,然后经过响应拦截器处理后再返回给用户。
但是,我们观察上面的`request`方法,在组成调用链的时候,我们只把`dispatchRequest`函数放到了调用链中,而`dispatchRequest`函数内部调用的`xhr`函数是异步的,它返回的是`Promise`对象,而调用链中处理的是`config`对象,并不是`Promise`对象,所以这样写是有问题的。
我们需要修改`dispatchRequest`函数,让它返回一个`Promise`对象,并且在`Promise`对象内部执行`xhr`函数,如下:
```typescript
function dispatchRequest(config: AxiosRequestConfig): AxiosPromise {
return new Promise((resolve, reject) => {
processConfig(config);
xhr(config).then(
(res) => {
resolve(transformResponseData(res));
},
(e) => {
reject(e);
}
);
});
}
```
这样,`dispatchRequest`函数返回的就是一个`Promise`对象,并且该`Promise`对象内部执行了`xhr`函数,这样就可以保证在调用链中,`dispatchRequest`函数返回的是`Promise`对象,然后调用链会等待该`Promise`对象`resolve`后,再把`resolve`的值传递给下一个拦截器。
# 4. 编写 demo
接下来,我们编写 `demo` 来测试下拦截器是否有效。
```javascript
const axios = new Axios({
baseURL: "http://localhost:3000",
});
// 添加请求拦截器
axios.interceptors.request.use(
(config) => {
console.log("request interceptor1 resolved");
return config;
},
(error) => {
console.log("request interceptor1 rejected");
return Promise.reject(error);
}
);
axios.interceptors.request.use(
(config) => {
console.log("request interceptor2 resolved");
return config;
},
(error) => {
console.log("request interceptor2 rejected");
return Promise.reject(error);
}
);
// 添加响应拦截器
axios.interceptors.response.use(
(res) => {
console.log("response interceptor1 resolved");
return res;
},
(error) => {
console.log("response interceptor1 rejected");
return Promise.reject(error);
}
);
axios.interceptors.response.use(
(res) => {
console.log("response interceptor2 resolved");
return res;
},
(error) => {
console.log("response interceptor2 rejected");
return Promise.reject(error);
}
);
axios.request({
url: "/api/base/get",
params: {
a: 1,
b: 2,
},
});
```
我们为`axios`的请求和响应分别添加了两个拦截器,并且在每个拦截器中都打印了相应的日志,另外,我们发送了一个简单的请求。
我们运行该`demo`,发现控制台打印顺序如下:
```javascript
request interceptor2 resolved
request interceptor1 resolved
response interceptor1 resolved
response interceptor2 resolved
```
从打印顺序可以看出:
- 请求拦截器是先添加的后执行(栈:先进后出);
- 响应拦截器是先添加的先执行(队列:先进先出);
另外,我们观察`network`中发送的请求,发现请求已经正常发出,并且返回了响应。

OK,这样我们就为`axios`实现了拦截器功能。
# 5. 遗留问题
虽然我们已经为`axios`实现了拦截器功能,但是目前还是存在一些问题的。
## 5.1 请求拦截器异常情况
如果请求拦截器中抛出异常,或者返回`Promise.reject(error)`,那么会怎样?
我们来修改`demo`,在第二个请求拦截器中返回`Promise.reject(error)`,如下:
```javascript
// 添加请求拦截器
axios.interceptors.request.use(
(config) => {
console.log("request interceptor1 resolved");
return config;
},
(error) => {
console.log("request interceptor1 rejected");
return Promise.reject(error);
}
);
axios.interceptors.request.use(
(config) => {
console.log("request interceptor2 resolved");
return Promise.reject("error in request interceptor2");
},
(error) => {
console.log("request interceptor2 rejected");
return Promise.reject(error);
}
);
```
然后运行`demo`,发现控制台打印顺序如下:
```javascript
request interceptor2 resolved
request interceptor1 rejected
response interceptor1 rejected
response interceptor2 rejected
```
从打印顺序可以看出:
- 第二个请求拦截器`resolved`函数中返回了`Promise.reject("error in request interceptor2")`,所以该拦截器的`resolved`函数返回的是一个`rejected`状态的`Promise`对象;
- 然后调用链会执行该拦截器的`rejected`函数,但是该拦截器我们并没有设置`rejected`函数,所以会跳过,然后调用链会继续执行下一个拦截器,即第一个请求拦截器,但是第一个请求拦截器的`resolved`函数并没有执行,而是执行了它的`rejected`函数,并且把第二个请求拦截器`resolved`函数返回的`rejected`状态的`Promise`对象的`reason`作为参数传递给了第一个请求拦截器的`rejected`函数;
- 接着调用链继续执行,由于第一个请求拦截器的`rejected`函数返回的也是`rejected`状态的`Promise`对象,所以会跳过所有响应拦截器的`resolved`函数,而是执行它们的`rejected`函数;
所以,最终请求并没有发送出去,而是走到了响应拦截器的`rejected`函数中。
## 5.2 响应拦截器异常情况
如果响应拦截器中抛出异常,或者返回`Promise.reject(error)`,那么会怎样?
我们来修改`demo`,在第二个响应拦截器中返回`Promise.reject(error)`,如下:
```javascript
// 添加响应拦截器
axios.interceptors.response.use(
(res) => {
console.log("response interceptor1 resolved");
return res;
},
(error) => {
console.log("response interceptor1 rejected");
return Promise.reject(error);
}
);
axios.interceptors.response.use(
(res) => {
console.log("response interceptor2 resolved");
return Promise.reject("error in response interceptor2");
},
(error) => {
console.log("response interceptor2 rejected");
return Promise.reject(error);
}
);
```
然后运行`demo`,发现控制台打印顺序如下:
```javascript
request interceptor2 resolved
request interceptor1 resolved
response interceptor1 resolved
response interceptor2 resolved
```
从打印顺序可以看出:
- 请求拦截器都正常执行了;
- 然后发送了请求,并且收到了响应;
- 接着执行响应拦截器,第一个响应拦截器的`resolved`函数正常执行,然后返回了`res`;
- 接着执行第二个响应拦截器的`resolved`函数,但是该函数返回了`Promise.reject("error in response interceptor2")`,所以该拦截器的`resolved`函数返回的是一个`rejected`状态的`Promise`对象;
- 然后调用链会执行该拦截器的`rejected`函数,但是该拦截器我们并没有设置`rejected`函数,所以会跳过,然后调用链会继续执行下一个拦截器,但是后面已经没有拦截器了,所以最终返回的是第二个响应拦截器`resolved`函数返回的`rejected`状态的`Promise`对象;
所以,最终请求发送出去了,并且收到了响应,但是响应拦截器返回的是`rejected`状态的`Promise`对象,所以用户接收到的响应是`rejected`状态的。
## 5.3 总结
从以上两种异常情况可以看出,我们的拦截器在处理异常情况时,行为是符合预期的。但是,我们目前并没有对异常情况进行处理,比如在拦截器中抛出异常,或者返回`Promise.reject(error)`,我们只是简单的打印了日志,并没有做其他处理。在实际项目中,我们可能需要对异常情况进行统一处理,比如弹出错误提示等。
# 6. 示例代码
至此,我们已经为`axios`实现了拦截器功能,完整代码见:[https://github.com/wangta69/learn-axios](https://github.com/wangta69/learn-axios)
# 7. 总结
在本篇文章中,我们首先修改了`Axios`类,为其添加了拦截器管理类的实例属性,然后修改了`request`方法,把拦截器的链式调用逻辑添加到该方法中,接着修改了`dispatchRequest`函数,让其返回一个`Promise`对象,并且在`Promise`对象内部执行`xhr`函数,最后我们编写了`demo`测试拦截器是否有效,并且分析了拦截器在处理异常情况时的行为。
从下一篇文章开始,我们将开始对`axios`的配置化进行重构。
最新文章
- 智能驾驶与新能源革命:车联网引领未来出行新趋势
- 电动化浪潮下:电池技术突破与智能驾驶新纪元
- 发动机气门正时优化指南:提升动力与燃油效率的关键
- 激光雷达与3D点云技术:开启自动驾驶新时代
- 2023年自动驾驶与固态电池技术革命性突破
- 智能汽车三大核心传感器:毫米波雷达、激光雷达与超声波技术解析
- V2X通信与车载传感器如何重塑智能驾驶体验
- 固态电池与800V快充:电动车电池技术的三大革命性突破
- 追尾事故如何避免汽车碰撞
- 铝合金轻量化汽车车身设计
- 出租车、私家车、新能源车最全车险保障方案解析
- 车主必读:伤残赔偿与盗抢险的全面保障指南
- 轮胎磨损影响汽车行驶安全定期检查
- 车龄与保养指南:不同阶段养护重点及费用解析
- 智能驾驶与充电网络:未来出行的三大技术变革
- 金杯汽车品质与荣耀
- 新手必学:5大驾驶技巧与安全保养全攻略
- 车损险保障爱车安全无忧
- 碳纤维与铝合金革新:汽车一体化设计与电子系统整合趋势
- 智能座舱与固态电池:未来汽车的三大技术变革
