当前位置 : 首页 > 保养数据

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. 链式调用实现 我们先来回顾一下拦截器执行流程: ![](~@/axios/05/01.png) 从图中可以看出,我们发送一个请求,会执行请求拦截器,然后执行`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测试结果来看,我们实现的拦截器功能是符合预期的,但是还有一些细节需要完善,比如异步逻辑和错误处理,这些我们会在后面的文章中逐步完善。

栏目列表