《Axios拦截器实现:请求响应全链路控制》
<|begin▁of▁sentence|># 1. 前言
在上一篇文章中,我们实现了`axios`的请求和响应配置化,即用户可以配置`url`、`method`、`params`、`data`、`headers`、`timeout`、`responseType`等。另外,我们还实现了配置的默认值以及配置的合并策略。那么接下来,我们就来实现`axios`的核心功能:**拦截器**。
# 2. 需求分析
在发送请求之前,我们往往需要对我们请求的参数做一些处理;而在收到响应之后,我们也需要对响应数据做一些处理。当有多个`axios`实例的情况下,每个实例需要的处理可能不同,所以这种处理函数我们就不适合在`axios`的核心代码中实现,而是应该暴露给用户,让用户自行编写,并且可以灵活的配置什么时候执行什么处理函数。那么,拦截器`interceptors` 就是来做这件事情的。
在发送请求之前,用户可以配置请求拦截器;在收到响应之后,用户可以配置响应拦截器。并且,请求拦截器可以配置多个,同样响应拦截器也可以配置多个,这多个拦截器按照用户配置的顺序来执行。
另外,在真实项目中,我们往往在请求拦截器中给请求添加`token`字段来保持用户的登录状态,而在响应拦截器中,我们会根据服务器返回的错误码来判断用户的登录状态是否过期,从而执行相应的操作。
# 3. 拦截器管理类
由于拦截器可以设置多个,并且每个拦截器都可以添加成功和失败的回调函数,所以我们可以把拦截器定义成一个对象,并且对外提供添加、删除、遍历的方法。我们创建一个`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` 泛型类,并且提供了三个方法`use`、`forEach`、`eject`:
- `use`: 添加一个拦截器,返回拦截器`id`,用于删除;
- `forEach`:遍历所有拦截器;
- `eject`:删除一个拦截器;
另外,`InterceptorManager` 类内部维护了一个私有属性 `interceptors`,它是一个数组,用来存储拦截器。
# 4. 给 Axios 添加拦截器
我们给`Axios`类添加`interceptors`属性,该属性包括两个拦截器:请求拦截器和响应拦截器。
```typescript
// src/core/Axios.ts
import { InterceptorManager } from "./InterceptorManager";
import { dispatchRequest, transformURL } from "./dispatchRequest";
import mergeConfig from "./mergeConfig";
import { AxiosInstance, AxiosRequestConfig, AxiosResponse, Method } from "../types";
import { Interceptors } from "./Interceptors";
export default class Axios {
defaults: AxiosRequestConfig;
interceptors: Interceptors;
constructor(initConfig: AxiosRequestConfig) {
this.defaults = initConfig;
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;
}
config = mergeConfig(this.defaults, config);
// 转换url
config.url = transformURL(config);
// 将方法名转换为小写
if (config.method) {
config.method = config.method.toLowerCase();
} else {
config.method = "get";
}
// 初始化拦截器
const chain: ResolvedChain[] = [
{
resolved: dispatchRequest,
rejected: undefined,
},
];
// 添加请求拦截器
this.interceptors.request.forEach((interceptor) => {
chain.unshift(interceptor);
});
// 添加响应拦截器
this.interceptors.response.forEach((interceptor) => {
chain.push(interceptor);
});
// 初始化promise
let promise = Promise.resolve(config);
// 执行拦截器
while (chain.length) {
const { resolved, rejected } = chain.shift()!;
promise = promise.then(resolved, rejected);
}
return promise;
}
get(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithoutData("get", url, config);
}
delete(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithoutData("delete", url, config);
}
head(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithoutData("head", url, config);
}
options(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithoutData("options", url, config);
}
post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithData("post", url, data, config);
}
put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithData("put", url, data, config);
}
patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithData("patch", url, data, config);
}
_requestMethodWithoutData(method: Method, url: string, config?: AxiosRequestConfig) {
return this.request(
Object.assign(config || {}, {
method,
url,
})
);
}
_requestMethodWithData(method: Method, url: string, data?: any, config?: AxiosRequestConfig) {
return this.request(
Object.assign(config || {}, {
method,
url,
data,
})
);
}
}
```
代码说明:
- 我们在`Axios`的构造函数中给实例添加了`interceptors`属性,它是一个对象,该对象包含两个属性:`request`和`response`,它们都是`InterceptorManager`的实例。
- 在`request`方法中,我们创建了一个数组`chain`,并把`dispatchRequest`函数赋值给`resolved`属性;然后先遍历请求拦截器插入到`chain`的前面;然后再遍历响应拦截器插入到`chain`的后面。
- 接下来定义一个已经 `resolve` 的 `promise`,循环这个 `chain`,拿到每个拦截器对象,把请求拦截器 `resolved` 函数和 `rejected` 函数添加到 `promise.then` 的参数中,这样就相当于通过 `Promise` 的链式调用方式,实现了拦截器一层层的链式调用的效果。
注意:我们拦截器的执行顺序是对于请求拦截器,先执行后添加的,再执行先添加的;而对于响应拦截器,先执行先添加的,后执行后添加的。
# 5. 修改类型定义
由于我们新创建了拦截器管理类,并且给`Axios`类添加了`interceptors`属性,所以我们需要在类型定义文件中添加相应的类型定义。
```typescript
// src/types/index.ts
export type Method =
| "get"
| "GET"
| "delete"
| "DELETE"
| "head"
| "HEAD"
| "options"
| "OPTIONS"
| "post"
| "POST"
| "put"
| "PUT"
| "patch"
| "PATCH";
export interface AxiosRequestConfig {
url?: string;
method?: Method;
data?: any;
params?: any;
headers?: any;
responseType?: XMLHttpRequestResponseType;
timeout?: number;
transformRequest?: AxiosTransformer | AxiosTransformer[];
transformResponse?: AxiosTransformer | AxiosTransformer[];
[propName: string]: any;
}
export interface AxiosResponse {
data: T;
status: number;
statusText: string;
headers: any;
config: AxiosRequestConfig;
request: any;
}
export interface AxiosPromise extends Promise> {}
export interface AxiosError extends Error {
config: AxiosRequestConfig;
code?: string;
request?: any;
response?: AxiosResponse;
isAxiosError?: boolean;
}
export interface Axios {
defaults: AxiosRequestConfig;
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;
}
export interface AxiosInstance extends Axios {
(config: AxiosRequestConfig): AxiosPromise;
(url: string, config?: AxiosRequestConfig): AxiosPromise;
}
export interface AxiosInterceptorManager {
use(resolved: ResolvedFn, rejected?: RejectedFn): number;
eject(id: number): void;
}
export interface ResolvedFn {
(val: T): T | Promise;
}
export interface RejectedFn {
(error: any): any;
}
export interface Interceptors {
request: InterceptorManager;
response: InterceptorManager;
}
export interface ResolvedChain {
resolved: ResolvedFn | ((config: AxiosRequestConfig) => AxiosPromise);
rejected?: RejectedFn;
}
export interface AxiosTransformer {
(data: any, headers?: any): any;
}
```
另外,我们还需要在`src/core`目录下创建`Interceptors.ts`文件,用来定义`Interceptors`接口,如下:
```typescript
// src/core/Interceptors.ts
import { InterceptorManager } from "./InterceptorManager";
import { AxiosRequestConfig, AxiosResponse } from "../types";
export interface Interceptors {
request: InterceptorManager;
response: InterceptorManager;
}
```
OK,类型定义添加好了,接下来我们就可以编写 demo 来测试拦截器功能了。
# 6. demo 编写
在 `examples` 目录下创建 `interceptors`目录,在 `interceptors`目录下创建 `index.`:
```
< lang="en">
interceptors demo
>
```
再创建 `app.ts` 作为入口文件:
```typescript
// examples/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: "Hello ",
},
})
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
```
接着在 `server/server.js` 添加新的接口路由:
```javascript
// 拦截器
router.get("/interceptor/get", function(req, res) {
res.end("hello interceptor");
});
```
最后在根目录下的`index.`中加上启动该`demo`的入口:
```
interceptors
```
OK,我们在命令行中执行:
```bash
# 同时开启客户端和服务端
npm run server | npm start
```
接着我们打开 `chrome` 浏览器,访问 即可访问我们的 `demo` 了,我们点击 `interceptors`,通过`F12`的 `network` 部分我们可以看到请求已正常发出,并且我们可以观察到请求的`headers`里面已经添加了我们写的两个请求拦截器所添加的字段,并且响应数据也添加了两个响应拦截器所添加的字段,如下:



OK,以上就是拦截器的实现。
最新文章
- 固态电池驱动未来汽车新动力
- 智能网联与新能源革命:自动驾驶技术重塑未来出行
- 汽车未来发展趋势
- 固态电池革命:能量密度提升50%,快充技术突破充电瓶颈
- 汽车发动机性能优化提升燃油效率
- 铜合金在汽车制造中广泛应用提升性能
- 后驱车驾驶乐趣与操控优势
- 激光雷达与多传感器融合:自动驾驶技术的演进与未来展望
- 高压平台车联网通信协议:技术演进与安全应用解析
- 本田汽车创新科技引领未来
- 智能驾驶革命:车联网与新能源重塑未来出行新生态
- 自动驾驶技术突破:激光雷达与车联网引领未来出行革命
- 五菱汽车创新之路
- 交通事故处理全攻略:从现场处理到保险理赔
- 智能车机与新能源技术:现代汽车科技全解析
- 智能驾驶技术加速演进:激光雷达与V2X引领汽车产业变革
- 拖挂房车驾驶指南:牵引能力、制动系统与中置轴要点解析
- 智能驾驶与新能源:重塑未来出行的三大核心技术
- 奔驰驾驶体验豪华舒适
- 未来汽车科技三大趋势:智能驾驶、新能源与车联网革新
