Axios拦截器实现:请求与响应的全流程控制
<|begin▁of▁sentence|># 1. 前言
在之前的文章中,我们介绍了`axios`的拦截器,以及拦截器与请求响应配置的合并,并且知道了拦截器是如何注册以及如何调用的。但是之前我们只实现了拦截器的管理结构,即`AxiosInterceptorManager`类,并没有在`Axios`类中真正实现添加拦截器的实例方法,即`use`、`eject`方法。那么从这篇文章开始,我们就来为`axios`实现拦截器功能。
# 2. 需求分析
我们先来看一下`axios`官方的拦截器是如何使用的。
## 2.1 使用示例
```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`方法为`request`和`response`分别添加拦截器,并且`use`方法支持两个参数,第一个参数类似`Promise`的`resolve`函数,第二个参数类似`Promise`的`reject`函数。我们可以在`resolve`函数和`reject`函数中执行同步代码或者是异步代码逻辑;
- 并且我们是可以添加多个拦截器的,拦截器的执行顺序是链式依次执行的方式。对于`request`拦截器,后添加的拦截器会在请求前的过程中先执行;对于`response`拦截器,先添加的拦截器会在响应后先执行;
## 2.2 实现思路
- 在`Axios`类中定义`interceptors`属性,该属性又有两个子属性`request`和`response`,它们俩都是`AxiosInterceptorManager`类的实例;
- 在`AxiosInterceptorManager`类中实现`use`实例方法,该方法支持两个参数,第一个参数是`resolve`函数,第二个参数是`reject`函数,并且该方法返回一个`id`用于删除拦截器;
- 在`AxiosInterceptorManager`类中实现`eject`实例方法,该方法支持一个参数,即拦截器`id`,调用该方法可以删除对应的拦截器;
- 在`Axios`类的`request`方法内部,我们需要创建一个拦截器链,然后让请求拦截器、发送请求、响应拦截器依次链式调用执行;
OK,以上就是我们实现拦截器功能的大体思路,接下来,我们就来一步步实现它。
# 3. 实现拦截器管理器类
我们先来实现拦截器管理器类`AxiosInterceptorManager`,该类我们已经创建好,在`src/core/AxiosInterceptorManager.ts`中,我们先来回顾一下该类中之前都写了什么:
```typescript
// src/core/AxiosInterceptorManager.ts
import { ResolvedFn, RejectedFn } from "../types";
interface Interceptor {
resolved: ResolvedFn;
rejected?: RejectedFn;
}
export default class AxiosInterceptorManager {
private interceptors: Array | null>;
constructor() {
this.interceptors = [];
}
use(resolved: ResolvedFn, rejected?: RejectedFn): number {
this.interceptors.push({
resolved,
rejected,
});
return this.interceptors.length - 1;
}
eject(id: number): void {
if (this.interceptors[id]) {
this.interceptors[id] = null;
}
}
}
```
该类中,我们定义了私有属性`interceptors`,它是一个数组,用来存储拦截器。该类还对外提供了两个实例方法`use`和`eject`,`use`用来添加拦截器,并返回一个`id`用于删除;`eject`用来删除指定`id`的拦截器。
但是,我们之前只写了这么多,并没有实现拦截器的遍历执行,那么接下来我们就来实现遍历执行拦截器。
我们给该类添加一个`forEach`方法,该方法接受一个函数`fn`作为参数,这个`fn`会接收每一个拦截器作为参数,并且我们可以在`fn`内部执行一些逻辑,例如:将拦截器添加到拦截器链中。
另外,我们在遍历拦截器的时候,需要注意:我们添加拦截器后可能会删除某些拦截器,当删除拦截器时,我们并不是真地从拦截器数组中删除该拦截器,而是把该拦截器置为`null`。所以我们在遍历的时候需要跳过为`null`的拦截器。
```typescript
// src/core/AxiosInterceptorManager.ts
import { ResolvedFn, RejectedFn } from "../types";
interface Interceptor {
resolved: ResolvedFn;
rejected?: RejectedFn;
}
export default class AxiosInterceptorManager {
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;
}
}
}
```
OK,拦截器管理器类就实现好了,接下来我们在`Axios`类中使用它。
# 4. 修改 Axios 类
首先,我们在`Axios`类中定义`interceptors`属性,该属性又有两个子属性`request`和`response`,它们俩都是`AxiosInterceptorManager`类的实例。
```typescript
// src/core/Axios.ts
import { Interceptors } from "./Interceptors";
export default class Axios {
defaults: AxiosRequestConfig;
interceptors: Interceptors;
constructor(initConfig: AxiosRequestConfig) {
this.defaults = initConfig;
this.interceptors = {
request: new AxiosInterceptorManager(),
response: new AxiosInterceptorManager(),
};
}
// ...
}
```
然后,我们还需要在`Axios`类的`request`方法内部,创建一个拦截器链,然后让请求拦截器、发送请求、响应拦截器依次链式调用执行。
在创建拦截器链之前,我们先来定义一下拦截器链的执行顺序:
- 请求拦截器 -> 发送请求 -> 响应拦截器
但是,我们需要注意:请求拦截器的执行顺序是后添加先执行,而响应拦截器的执行顺序是先添加先执行。
另外,我们还要注意:在发送请求之前,我们还要将请求配置与默认配置进行合并,并且将`headers`进行深度合并,这些我们之前都已经实现了。
OK,接下来我们就来创建拦截器链。
首先,我们创建一个数组`chain`,用来存放拦截器链。默认情况下,我们先把发送请求`dispatchRequest`函数放在数组的中间位置。
```typescript
// src/core/Axios.ts
import dispatchRequest from "./dispatchRequest";
request(url: any, config?: any): AxiosPromise {
if (typeof url === 'string') {
if (!config) {
config = {};
}
config.url = url;
} else {
config = url;
}
// 将默认配置与用户传入的配置进行合并
config = mergeConfig(this.defaults, config);
// 设置配置的请求方法,默认为get
config.method = config.method.toLowerCase() || methods[0];
// 创建拦截器链
const chain: any[] = [
{
resolved: dispatchRequest,
rejected: undefined,
},
];
}
```
然后,我们分别遍历请求拦截器和响应拦截器,将它们的`resolved`函数和`rejected`函数添加到`chain`数组的两端。
```typescript
// src/core/Axios.ts
request(url: any, config?: any): AxiosPromise {
if (typeof url === 'string') {
if (!config) {
config = {};
}
config.url = url;
} else {
config = url;
}
// 将默认配置与用户传入的配置进行合并
config = mergeConfig(this.defaults, config);
// 设置配置的请求方法,默认为get
config.method = config.method.toLowerCase() || methods[0];
// 创建拦截器链
const chain: any[] = [
{
resolved: dispatchRequest,
rejected: undefined,
},
];
// 遍历请求拦截器,将其添加到chain数组的前面
this.interceptors.request.forEach((interceptor) => {
chain.unshift(interceptor);
});
// 遍历响应拦截器,将其添加到chain数组的后面
this.interceptors.response.forEach((interceptor) => {
chain.push(interceptor);
});
}
```
接下来,我们创建一个`promise`,初始时`promise`的状态为`resolved`,值为合并后的请求配置`config`。
```typescript
// src/core/Axios.ts
request(url: any, config?: any): AxiosPromise {
if (typeof url === 'string') {
if (!config) {
config = {};
}
config.url = url;
} else {
config = url;
}
// 将默认配置与用户传入的配置进行合并
config = mergeConfig(this.defaults, config);
// 设置配置的请求方法,默认为get
config.method = config.method.toLowerCase() || methods[0];
// 创建拦截器链
const chain: any[] = [
{
resolved: dispatchRequest,
rejected: undefined,
},
];
// 遍历请求拦截器,将其添加到chain数组的前面
this.interceptors.request.forEach((interceptor) => {
chain.unshift(interceptor);
});
// 遍历响应拦截器,将其添加到chain数组的后面
this.interceptors.response.forEach((interceptor) => {
chain.push(interceptor);
});
// 创建一个promise,初始时promise的状态为resolved,值为合并后的请求配置config
let promise = Promise.resolve(config);
}
```
最后,我们通过`while`循环,不断地从`chain`数组中取出拦截器,然后依次执行。
```typescript
// src/core/Axios.ts
request(url: any, config?: any): AxiosPromise {
if (typeof url === 'string') {
if (!config) {
config = {};
}
config.url = url;
} else {
config = url;
}
// 将默认配置与用户传入的配置进行合并
config = mergeConfig(this.defaults, config);
// 设置配置的请求方法,默认为get
config.method = config.method.toLowerCase() || methods[0];
// 创建拦截器链
const chain: any[] = [
{
resolved: dispatchRequest,
rejected: undefined,
},
];
// 遍历请求拦截器,将其添加到chain数组的前面
this.interceptors.request.forEach((interceptor) => {
chain.unshift(interceptor);
});
// 遍历响应拦截器,将其添加到chain数组的后面
this.interceptors.response.forEach((interceptor) => {
chain.push(interceptor);
});
// 创建一个promise,初始时promise的状态为resolved,值为合并后的请求配置config
let promise = Promise.resolve(config);
// 通过while循环,不断地从chain数组中取出拦截器,然后依次执行
while (chain.length) {
const { resolved, rejected } = chain.shift()!;
promise = promise.then(resolved, rejected);
}
return promise;
}
```
至此,我们就实现了拦截器功能。
# 5. 编写 demo
接下来,我们编写一个 `demo` 来测试下我们实现的拦截器功能是否正常。
我们在 `examples` 目录下创建 `interceptors` 目录,在 `interceptors` 目录下创建 `index.`:
```
< lang="en">
interceptors demo
>
```
接着再创建 `app.ts` 作为入口文件:
```typescript
// examples/interceptors/app.ts
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);
});
```
该 `demo` 中我们添加了 3 个请求拦截器,往 `headers` 添加了 3 个数字,添加了 3 个响应拦截器,往 `data` 添加了 3 个数字并且删除了第二个响应拦截器。因此,最终我们应该在控制台看到 `data` 的结果为 `13`,并且请求的 `headers` 中的 `test` 字段为 `123`。
接着在 `examples/interceptors` 目录下创建一个 `server.js` 文件,用来服务端响应我们的请求,由于我们请求的 `url` 是 `/interceptor/get`,我们只需要给这个 `url` 返回一个字符串 `hello interceptor` 即可。
```javascript
// examples/interceptors/server.js
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get("/interceptor/get", function (req, res) {
res.end("hello interceptor");
});
const port = 3000;
module.exports = app.listen(port, () => {
console.log(`Server listening on http://localhost:${port}, Ctrl+C to stop`);
});
```
接着在 `examples/interceptors` 目录下再创建一个 `index.`,并写入如下代码:
```
< lang="en">
interceptors demo
>
```
最后,在根目录下的 `index.` 中加上启动该 `demo` 的入口:
```
interceptors
```
OK, 我们最后在命令行中执行:
```bash
# 同时开启客户端和服务端
npm run server | npm start
```
接着我们打开 `chrome` 浏览器,访问 即可访问我们的 `demo` 了,我们点击 `interceptors`,通过`F12`的 `network` 部分我们可以看到请求已正常发出,并且请求的 `headers` 中已经添加了 `test: 123`,并且响应返回的 `data` 为 `hello interceptor13`,在控制台也可以打印出该数据。


OK,以上就是拦截器功能的实现。
最新文章
- 刹车失灵危险大!应急处理与预防保养全指南
- V2X通信与ADAS系统:车联网技术重塑未来智能出行
- 充电桩与电动汽车:破解续航焦虑的未来之路
- 电动化浪潮:电池技术革新与智能驾驶的未来趋势
- 智能网联与自动驾驶技术重塑未来出行新生态
- 激光雷达:自动驾驶汽车的智慧之眼
- 新能源汽车充电桩:技术突破与未来布局全景分析
- 长安欧尚汽车驾享未来
- 智能网联与新能源驱动未来交通变革
- 现代汽车科技革新未来
- 底盘调校与排量匹配:解析汽车动力性能的关键要素
- 轮胎磨损类型解析:从胎面厚度到四轮定位的全面指南
- 冷却液保养与方向盘握法:行车安全的两大关键细节
- 智能座舱与固态电池:未来汽车科技的三大突破
- 固态电池与毫米波雷达领跑汽车电动化与智能化革命
- 电动汽车自动驾驶充电技术:现状与未来发展趋势
- 电动化与智能化双轮驱动:2025年汽车产业变革全景展望
- 车载灭火器与ABS系统:行车安全防护全指南
- 智能驾驶与固态电池革新:未来汽车三大技术趋势解析
- 特斯拉Model 3续航里程突破600公里
