当前位置 : 首页 > 维修数据

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`,在控制台也可以打印出该数据。 ![](~@/axios/17/01.png) ![](~@/axios/17/02.png) OK,以上就是拦截器功能的实现。

    栏目列表