当前位置 : 首页 > 购买指南

《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`里面已经添加了我们写的两个请求拦截器所添加的字段,并且响应数据也添加了两个响应拦截器所添加的字段,如下: ![](~@/axios/13/01.png) ![](~@/axios/13/02.png) ![](~@/axios/13/03.png) OK,以上就是拦截器的实现。

    栏目列表