Axios拦截器实现:请求响应全流程控制
<|begin▁of▁sentence|># 1. 前言
在上一篇文章中,我们实现了`axios`的请求和响应配置化,即用户可以配置`url`、`method`、`params`、`data`、`headers`、`timeout`、`responseType`等等。另外,我们还实现了配置的默认值以及配置的合并策略。那么接下来,我们就来实现`axios`的核心功能:**拦截器**。
# 2. 拦截器介绍
`axios`拦截器的作用是我们在发送请求之前和响应之后可以做一些操作,例如:在发送请求之前,在请求头中添加`token`字段;在响应之后,我们根据`http`状态码来判断用户的登录状态是否过期等等。
`axios`拦截器分为两种:请求拦截器和响应拦截器。
- 请求拦截器:在发送请求之前做一些操作。
- 响应拦截器:在响应之后做一些操作。
并且,`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);
}
);
```
另外,我们还可以随时删除拦截器,例如:
```javascript
const myInterceptor = axios.interceptors.request.use(function() {
/*...*/
});
axios.interceptors.request.eject(myInterceptor);
```
# 3. 实现思路
通过上面的介绍,我们可以总结出如下几点:
- 拦截器分为请求拦截器和响应拦截器两种;
- 可以为同一个`axios`实例添加多个拦截器,拦截器的执行顺序按照添加顺序执行;
- 拦截器也支持成功和失败回调,并且支持返回`Promise`;
- 我们还可以随时删除拦截器;
那么,接下来,我们就来实现拦截器。
## 3.1 接口定义
根据需求,我们需要给`axios`对象添加一个`interceptors`属性,该属性又有两个属性:`request`和`response`,它们都是拦截器管理类`InterceptorManager`的实例。
```typescript
export interface Axios {
// 新增interceptors属性
interceptors: {
request: InterceptorManager;
response: InterceptorManager;
};
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;
}
```
## 3.2 拦截器管理类-InterceptorManager
根据需求,我们需要实现一个拦截器管理类,这个类需要提供三个方法:`use`、`eject`、`forEach`。
- `use`方法用来添加拦截器,它接收两个参数:成功回调和失败回调,并且会返回一个`id`用于删除该拦截器;
- `eject`方法用来删除拦截器,它接收一个参数,即`use`方法返回的`id`;
- `forEach`方法用来遍历所有拦截器,它接收一个参数,该参数是一个函数,该函数会接收每一个拦截器作为参数;
并且,该类还需要内部维护一个数组,用来存储添加的拦截器。
OK,思路理清之后,我们就来实现这个拦截器管理类。
我们在`src`目录下创建`core`目录,用来存放核心代码,然后在`core`目录下创建`InterceptorManager.ts`文件。
```typescript
// src/core/InterceptorManager.ts
export interface OnFulfilled {
(value: V): V | Promise;
}
export interface OnRejected {
(error: any): any;
}
export interface Interceptor {
onFulfilled?: OnFulfilled;
onRejected?: OnRejected;
}
export default class InterceptorManager {
public interceptors: Array | null> = [];
use(onFulfilled?: OnFulfilled, onRejected?: OnRejected): number {
this.interceptors.push({
onFulfilled,
onRejected,
});
return this.interceptors.length - 1;
}
eject(id: number) {
if (this.interceptors[id]) {
this.interceptors[id] = null;
}
}
forEach(fn: (interceptor: Interceptor) => void): void {
this.interceptors.forEach(interceptor => {
if (interceptor !== null) {
fn(interceptor);
}
});
}
}
```
代码说明:
- 我们定义了一个`InterceptorManager`泛型类,并且定义了一个公共属性`interceptors`,它是一个数组,数组的每一项是一个`Interceptor`类型的对象或`null`,初始值为空数组。
- `use`方法接收两个参数:`onFulfilled`和`onRejected`,这两个参数都是可选的。在函数内部,我们把这两个参数组合成一个对象`Interceptor`并`push`到`interceptors`中,并且返回它在数组中的索引`id`。
- `eject`方法接收一个参数`id`,即`use`方法返回的`id`,在函数内部,我们根据`id`找到`interceptors`中对应的拦截器,然后将其置为`null`。这里我们并没有使用`splice`等方法将该拦截器从数组中删除,而是置为`null`,主要是因为当我们遍历拦截器的时候,遍历的是`interceptors`,而`interceptors`中可能有些项是`null`,所以我们遍历的时候需要把`null`的项过滤掉,这样做是为了避免当我们删除拦截器后,`interceptors`中每一项的索引发生变化,从而导致`eject`方法删除错误的拦截器。
- `forEach`方法接收一个参数`fn`,该参数是一个函数,该函数会接收每一个拦截器作为参数。在函数内部,我们遍历`interceptors`,并且将不为`null`的拦截器作为参数传给`fn`。
## 3.3 给 Axios 类添加 interceptors 属性
定义好拦截器管理类之后,我们就需要给`Axios`类添加`interceptors`属性,并且初始化`request`和`response`。
我们在`src`目录下的`core`目录中创建`Axios.ts`文件。
```typescript
// src/core/Axios.ts
import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from "../types";
import dispatchRequest from "./dispatchRequest";
import InterceptorManager from "./InterceptorManager";
// 定义拦截器接口
interface Interceptors {
request: InterceptorManager;
response: InterceptorManager;
}
export default class Axios {
// 公共属性
public interceptors: Interceptors;
constructor() {
// 初始化interceptors
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
request(config: AxiosRequestConfig): AxiosPromise {
return dispatchRequest(config);
}
get(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "get",
url,
})
);
}
delete(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "delete",
url,
})
);
}
head(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "head",
url,
})
);
}
options(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "options",
url,
})
);
}
post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "post",
url,
data,
})
);
}
put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "put",
url,
data,
})
);
}
patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "patch",
url,
data,
})
);
}
}
```
OK,`interceptors`属性添加好了,接下来我们就要修改`request`方法,在发送请求之前先执行请求拦截器,在接收到响应之后先执行响应拦截器。
## 3.4 实现拦截器链
在发送请求之前,我们先要执行所有的请求拦截器,然后再发送请求;在接收到响应之后,我们先要执行所有的响应拦截器,然后再将响应数据返回。所以,我们需要在`request`方法中实现拦截器链。
思路如下:
1. 首先,我们创建一个数组`chain`,该数组保存的默认项是发送请求的函数`dispatchRequest`和`undefined`。这里为什么要有`undefined`呢?因为我们拦截器的`use`方法可以只传入一个成功回调,而失败回调是可选的,并且我们在组合`Promise`链的时候,数组的每两项分别对应成功回调和失败回调。所以这里我们先用`undefined`占个位,是为了保证`chain`中都是成对的。
2. 然后,我们遍历所有的请求拦截器,将每个请求拦截器的成功回调和失败回调`unshift`到`chain`数组的前面。
3. 接着,我们遍历所有的响应拦截器,将每个响应拦截器的成功回调和失败回调`push`到`chain`数组的后面。
4. 此时,`chain`数组应该是这样的:
```javascript
[
请求拦截器成功回调1, 请求拦截器失败回调1, 请求拦截器成功回调2, 请求拦截器失败回调2, ...,
dispatchRequest, undefined,
响应拦截器成功回调1, 响应拦截器失败回调1, 响应拦截器成功回调2, 响应拦截器失败回调2, ...
]
```
5. 然后,我们初始化一个`Promise`,`Promise`的`resolve`参数是 config,然后遍历`chain`,将`chain`的每两项(成功回调和失败回调)作为`then`的参数,从而形成一个`Promise`调用链。
OK,我们按照这个思路来修改`request`方法:
```typescript
// src/core/Axios.ts
request(config: AxiosRequestConfig): AxiosPromise {
// 初始化一个数组,存放拦截器和发送请求的函数
const chain: Array | InterceptorRejected | undefined> = [
{
resolved: dispatchRequest,
rejected: undefined,
},
];
// 遍历请求拦截器,插入到chain数组的前面
this.interceptors.request.forEach(interceptor => {
if (interceptor) {
chain.unshift(interceptor);
}
});
// 遍历响应拦截器,插入到chain数组的后面
this.interceptors.response.forEach(interceptor => {
if (interceptor) {
chain.push(interceptor);
}
});
// 初始化一个Promise,resolve参数是config
let promise = Promise.resolve(config);
// 循环chain数组,将每两项(成功回调,失败回调)作为then的参数,形成Promise调用链
while (chain.length) {
const { onFulfilled, onRejected } = chain.shift()!;
promise = promise.then(onFulfilled, onRejected);
}
return promise;
}
```
但是,我们这样写会报错,因为`chain`数组中的每一项可能是`Interceptor`类型,也可能是`{resolved: dispatchRequest, rejected: undefined}`类型,而这两种类型是不兼容的,所以我们需要统一`chain`数组中的每一项的类型。
我们观察`chain`数组中的每一项,无论是`Interceptor`类型还是`{resolved: dispatchRequest, rejected: undefined}`类型,它们都有`onFulfilled`和`onRejected`属性,所以我们可以定义一个接口,让这两种类型都实现这个接口。
我们在`src/core/InterceptorManager.ts`中定义这个接口:
```typescript
// src/core/InterceptorManager.ts
export interface Interceptor {
onFulfilled?: OnFulfilled;
onRejected?: OnRejected;
}
// 新增
export interface InterceptorResolved {
onFulfilled?: OnFulfilled;
onRejected?: OnRejected;
}
```
然后,修改`Axios.ts`中的`request`方法:
```typescript
// src/core/Axios.ts
request(config: AxiosRequestConfig): AxiosPromise {
// 初始化一个数组,存放拦截器和发送请求的函数
const chain: Array> = [
{
onFulfilled: dispatchRequest,
onRejected: undefined,
},
];
// 遍历请求拦截器,插入到chain数组的前面
this.interceptors.request.forEach(interceptor => {
if (interceptor) {
chain.unshift(interceptor);
}
});
// 遍历响应拦截器,插入到chain数组的后面
this.interceptors.response.forEach(interceptor => {
if (interceptor) {
chain.push(interceptor);
}
});
// 初始化一个Promise,resolve参数是config
let promise = Promise.resolve(config);
// 循环chain数组,将每两项(成功回调,失败回调)作为then的参数,形成Promise调用链
while (chain.length) {
const { onFulfilled, onRejected } = chain.shift()!;
promise = promise.then(onFulfilled, onRejected);
}
return promise;
}
```
这样,我们就实现了拦截器链。
## 3.5 修改 dispatchRequest 函数
由于我们拦截器链中第一个`Promise`的`resolve`参数是`config`,所以请求拦截器的成功回调的参数是`config`,而请求拦截器的成功回调可以返回一个新的`config`,也可以返回一个`Promise`,所以我们需要修改`dispatchRequest`函数,让它接收一个`config`参数,并且返回一个`Promise`。
我们在`src/core`目录下创建`dispatchRequest.ts`文件。
```typescript
// src/core/dispatchRequest.ts
import { AxiosRequestConfig, AxiosPromise, 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;
}
```
这里,我们把之前写在`xhr.ts`中的`processConfig`和`transformResponseData`函数移到了`dispatchRequest.ts`中,并且做了一些修改。
## 3.6 修改 xhr 函数
由于`dispatchRequest`函数已经处理了`config`,所以`xhr`函数中就不需要再处理`config`了,我们修改`xhr`函数:
```typescript
// src/core/xhr.ts
import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from "../types";
import { parseHeaders } from "../helpers/headers";
export default function xhr(config: AxiosRequestConfig): AxiosPromise {
return new Promise((resolve, reject) => {
const { data = null, url, method = "get", headers, responseType, timeout } = config;
const request = new XMLHttpRequest();
if (responseType) {
request.responseType = responseType;
}
if (timeout) {
request.timeout = timeout;
}
request.open(method.toUpperCase(), url!, true);
request.onreadystatechange = function handleLoad() {
if (request.readyState !== 4) {
return;
}
if (request.status === 0) {
return;
}
const responseHeaders = parseHeaders(request.getAllResponseHeaders());
const responseData = responseType !== "text" ? request.response : request.responseText;
const response: AxiosResponse = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request,
};
handleResponse(response);
};
request.onerror = function handleError() {
reject(new Error("Network Error"));
};
request.ontimeout = function handleTimeout() {
reject(new Error(`Timeout of ${timeout} ms exceeded`));
};
function handleResponse(response: AxiosResponse)
最新文章
- 保持安全车距确保行车安全
- 智能驾驶与新能源电池技术突破:2024车联网生态全面升级
- 刹车失灵危险大!应急处理与预防保养全指南
- 双向充电技术:让电动汽车变身移动储能站的未来革命
- 车漆保养全攻略:从日常护理到专业镀膜技巧
- 智能驾驶技术:传感器融合与车路协同的未来发展
- 汽车点火系统工作原理解析
- 磷酸铁锂电池:新能源汽车安全与经济性的双重突破
- 特斯拉电动跑车加速迅猛续航持久
- 电动化、智能化、车联网:汽车产业三大变革趋势解析
- 固态电池与高压快充引领智能座舱新浪潮
- 新车必备!底盘车损险全方位解析与选购指南
- 电动化浪潮:锂离子电池与高压平台驱动汽车产业变革
- 电动汽车技术突破:固态电池与高压平台如何解决续航焦虑
- 雷克萨斯豪华座驾新体验
- 汽车未来发展趋势
- 保时捷新款跑车速度与激情完美结合
- 电动汽车革命:三元锂电池与固态电池技术引领车联网新时代
- 固态电池突破:能量密度提升50%,充电速度翻3倍
- 燃油系统与汽车动力关系