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`函数中执行同步代码或者是异步代码逻辑。
并且我们是可以添加多个拦截器的,拦截器的执行顺序是链式依次执行的方式。对于`request`拦截器,后添加的拦截器会在请求前的过程中先执行;对于`response`拦截器,先添加的拦截器会在响应后先执行。
```javascript
axios.interceptors.request.use(config => {
config.headers.test += "1";
return config;
});
axios.interceptors.request.use(config => {
config.headers.test += "2";
return config;
});
```
另外,我们也可以支持删除某个拦截器,如下:
```javascript
const myInterceptor = axios.interceptors.request.use(function() {
/*...*/
});
axios.interceptors.request.eject(myInterceptor);
```
# 3. 整体设计
我们通过简单使用方式,已经知道拦截器是可以有多个的,所以它内部肯定是用数组来存储的,当我们执行`use`方法添加拦截器时,就会往数组中`push`一个拦截器对象,注意:`use`方法的参数是两个函数,一个是处理`resolve`逻辑的函数,一个是处理`reject`逻辑的函数。当我们执行`eject`方法删除拦截器时,就会从数组中删除该拦截器。
另外,对于`use`方法,它的返回值应该是一个`id`,以便`eject`方法可以通过这个`id`找到要删除的拦截器。
当我们实现好拦截器管理对象后,我们需要在`request`方法中实现整个拦截器的执行逻辑,以及它们之间的链式调用顺序。
# 4. 实现拦截器管理类
根据需求分析,我们先来定义一个拦截器管理类,这个类是公共的,它包含`request`和`response`的拦截器管理,并且提供`use`方法添加拦截器,`eject`方法删除拦截器,`forEach`方法遍历所有拦截器。
我们在`src`目录下创建`core`目录,用于存放与拦截器相关的代码。创建`InterceptorManager.ts`文件。
## 4.1 接口定义
我们需要定义一个接口,用来定义`use`方法的参数,即`resolve`函数和`reject`函数。
```typescript
// src/types/index.ts
export interface ResolvedFn {
(val: T): T | Promise;
}
export interface RejectedFn {
(error: any): any;
}
```
## 4.2 实现 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;
}
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`,它是一个数组,用来存储拦截器。该类还对外提供了 3 个方法,其中`use`接口就是添加拦截器到`interceptors`中,并返回一个`id`用于删除;`forEach`接口就是遍历`interceptors`用的,它支持传入一个函数,遍历过程中会调用该函数,并把每一个`interceptor`作为该函数的参数传入;`eject`就是删除一个拦截器,通过传入拦截器的`id`删除。
# 5. 修改 Axios 类型定义
我们需要在`Axios`类型中定义`interceptors`属性,如下:
```typescript
// src/types/index.ts
import InterceptorManager from "./core/InterceptorManager";
export interface Interceptors {
request: InterceptorManager;
response: InterceptorManager;
}
export interface Axios {
interceptors: Interceptors;
request(config: AxiosRequestConfig): AxiosPromise;
get(url: string, config?: AxiosRequestConfig): AxiosPromise;
delete(url: string, config?: AxiosRequestConfig): AxiosPromise;
head(url: string, config?: AxiosRequestConfig): AxiosPromise;
options(url: string, config?: AxiosRequestConfig): AxiosPromise;
post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise;
put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise;
patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise;
}
```
`Interceptors`类型拥有 2 个属性,一个请求拦截器管理类实例,一个是响应拦截器管理类实例。我们在`Axios`接口中加上了`interceptors`属性。
# 6. 创建 Interceptors 对象
接下来,我们修改`Axios`类,给`interceptors`属性赋值。
```typescript
// src/core/Axios.ts
import { Interceptors } from "../types";
import InterceptorManager from "./InterceptorManager";
export default class Axios {
public interceptors: Interceptors;
constructor() {
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
request(config: AxiosRequestConfig): AxiosPromise {
// ...
}
// ...
}
```
我们给`Axios`类添加一个`interceptors`属性,类型是`Interceptors`,然后在构造函数中实例化`request`和`response`拦截器管理类。
OK,接下来我们要重点实现`request`方法的逻辑,我们需要让请求拦截器、`dispatchRequest`、响应拦截器构成一个链式调用的`Promise`链。
# 7. 链式调用实现
我们先来回顾一下拦截器执行流程:

从图中可以看出,我们发送一个请求,会执行请求拦截器,然后执行`dispatchRequest`,再执行响应拦截器,最后返回响应。并且,请求拦截器是后添加先执行,响应拦截器是先添加先执行,这样就构成了一个链式调用。
另外,请求拦截器可以函数同步逻辑,也可以执行异步逻辑,并且我们可以在请求拦截器中拿到请求的配置对象`config`,并且可以对它做修改,这样我们就可以在请求拦截器中动态修改请求的配置。响应拦截器同样如此。
那么,如何实现这样一个链式Promise呢?我们可以把请求拦截器、`dispatchRequest`和响应拦截器都放在一个数组里,然后通过`Promise`的链式调用特性,让它们依次执行。
首先,我们构建一个数组,数组中存放的每一项都是一个对象,该对象包含2个函数属性,一个`resolved`函数,一个`rejected`函数,这两个函数分别对应`Promise`的`resolve`和`reject`。
然后,我们遍历这个数组,在`Promise`的链式调用中依次执行数组中的每个函数。
## 7.1 构建Promise链
我们先在`src/core/Axios.ts`中实现`request`方法:
```typescript
// src/core/Axios.ts
import { Interceptors, AxiosRequestConfig, AxiosPromise, AxiosResponse } from "../types";
import InterceptorManager from "./InterceptorManager";
import dispatchRequest from "./dispatchRequest";
interface PromiseChain {
resolved: ResolvedFn | ((config: AxiosRequestConfig) => AxiosPromise);
rejected?: RejectedFn;
}
export default class Axios {
public interceptors: Interceptors;
constructor() {
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
request(config: AxiosRequestConfig): AxiosPromise {
const chain: PromiseChain[] = [
{
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;
}
// ...
}
```
首先,我们定义了一个`PromiseChain`的接口,它描述了`Promise`链中每个节点的类型,它有2个属性,`resolved`函数和`rejected`函数。
然后,我们创建了一个数组`chain`,并把`dispatchRequest`函数赋值给`resolved`属性,我们先把`dispatchRequest`函数放在数组的中间。
接着,遍历请求拦截器,将请求拦截器插入到`chain`数组的前面,注意:这里使用`unshift`方法,所以后添加的请求拦截器会在数组的前面,因此会先执行。
然后,遍历响应拦截器,将响应拦截器插入到`chain`数组的后面,注意:这里使用`push`方法,所以后添加的响应拦截器会在数组的后面,因此会后执行。
接下来,我们定义一个已经`resolve`的`promise`,`Promise.resolve(config)`,`config`是请求的配置对象。
然后,通过`while`循环,依次取出`chain`中的每个节点,然后执行`promise.then(resolved, rejected)`,这样就实现了链式调用。
最后,返回`promise`。
## 7.2 修改dispatchRequest
我们之前实现的`dispatchRequest`函数,它返回的是一个`AxiosPromise`类型,也就是`Promise>`类型,但是我们在请求拦截器中拿到的是`AxiosRequestConfig`类型,而在响应拦截器中拿到的是`AxiosResponse`类型,所以我们需要修改`dispatchRequest`函数,让它返回的是`AxiosPromise`类型。
```typescript
// src/core/dispatchRequest.ts
import { AxiosPromise, AxiosRequestConfig, AxiosResponse } from "../types";
import xhr from "./xhr";
import { buildURL } from "../helpers/url";
import { transformRequest, transformResponse } from "../helpers/data";
import { processHeaders } from "../helpers/headers";
export default function dispatchRequest(config: AxiosRequestConfig): AxiosPromise {
processConfig(config);
return xhr(config).then(res => {
return transformResponseData(res);
});
}
function processConfig(config: AxiosRequestConfig): void {
config.url = transformURL(config);
config.headers = transformHeaders(config);
config.data = transformRequestData(config);
}
function transformURL(config: AxiosRequestConfig): string {
const { url, params } = config;
return buildURL(url!, params);
}
function transformRequestData(config: AxiosRequestConfig): any {
return transformRequest(config.data);
}
function transformHeaders(config: AxiosRequestConfig): any {
const { headers = {}, data } = config;
return processHeaders(headers, data);
}
function transformResponseData(res: AxiosResponse): AxiosResponse {
res.data = transformResponse(res.data);
return res;
}
```
我们修改了`dispatchRequest`函数,让它返回一个`Promise`,并且在`then`方法中处理响应数据,然后返回响应对象。
# 8. 编写demo
接下来,我们编写一个demo来测试一下拦截器功能。
在`examples`目录下创建`interceptor`目录,在`interceptor`目录下创建`index.`:
```
< lang="en">
interceptor demo
>
```
接着创建`app.ts`:
```typescript
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);
});
```
然后在命令行中执行:
```bash
# 同时开启服务
npm run server
# 新开一个终端,编译interceptor demo
npm run dev:interceptor
```
接着在浏览器中打开:`http://localhost:8000/`,接着打开`interceptor`目录下的页面,通过`F12`的控制台我们可以看到请求的发送情况和响应结果。
从控制台我们可以看到,我们发送的请求的请求头中`test`字段的值是`321`,这是因为我们添加了3个请求拦截器,每个拦截器都给`headers.test`添加了一个数字,分别是`1`、`2`、`3`,由于请求拦截器是后添加的先执行,所以最终`test`的值是`321`。
另外,从响应结果中我们可以看到,响应数据是`13`,这是因为我们添加了3个响应拦截器,每个拦截器都给响应数据添加了一个数字,分别是`1`、`2`、`3`,由于响应拦截器是先添加的先执行,所以最终响应数据应该是`123`,但是我们在中间删除了第二个响应拦截器,所以最终响应数据是`13`。
# 9. 遗留问题
我们虽然实现了拦截器的功能,但是还是有一些细节需要完善,比如:在请求拦截器中,我们是可以执行异步逻辑的,但是我们目前的实现中,如果请求拦截器中执行了异步逻辑,那么整个链式调用就会等待异步逻辑执行完毕后再继续执行,这样就会导致请求发送的延迟。
另外,我们目前没有对错误进行处理,如果请求拦截器或者响应拦截器中抛出了错误,那么整个链式调用就会中断,并且会返回一个`reject`的`Promise`。
这些问题我们会在后面的文章中逐步完善。
# 10. 总结
本篇文章我们实现了拦截器功能,首先我们分析了拦截器的使用方式,然后根据使用方式设计了拦截器的管理类,接着我们在`Axios`类中实现了拦截器的链式调用逻辑,最后我们编写了demo测试了拦截器的功能。
从demo测试结果来看,我们实现的拦截器功能是符合预期的,但是还有一些细节需要完善,比如异步逻辑和错误处理,这些我们会在后面的文章中逐步完善。
最新文章
- 汽车散热系统高效冷却引擎确保稳定运行
- 智能网联与新能源驱动未来交通变革
- 充电桩与电动汽车协同发展:技术革新与未来布局
- 充电桩与电动汽车:破解续航焦虑的未来之路
- 轻量化设计提升汽车燃油效率与性能
- 高强度钢材+盗抢险:双重守护爱车的安全屏障
- 电机散热器优化提升汽车性能
- 共享汽车如何破解找车难、停车贵、车况差三大痛点?
- 智能网联与自动驾驶驱动新能源出行革命
- 汽车悬挂系统解析与行车安全实用指南
- 遵守交通规则购买交强险保障行车安全
- 智能驾驶与车联网技术加速演进,800V高压平台成新趋势
- 智能座舱与固态电池:未来汽车科技的三大突破
- 激光雷达:自动驾驶的3D火眼金睛与未来挑战
- 智能座舱与车路协同:未来出行的四大变革趋势
- 汽车翼子板:设计要点、修复技巧与日常维护指南
- 当你的爱车开始“聊天”:C-V2X如何重塑未来出行
- 汽车与未来出行的绿色革命
- 智能座舱与固态电池引领未来,车路协同重塑智慧交通
- Axios拦截器实现:Promise链式调用与请求响应处理
