您现在的位置是:首页 > 编程语言学习 > 后端编程语言 > 文章正文 后端编程语言

axios拦截器管理类链式调用手写实现及原理剖析

2022-08-29 10:18:45 后端编程语言

简介axios库的拦截器使用我们知道axios库的拦截器的使用方式如下://添加一个请求拦截器axios.interceptors.request.use(function(config){//在...

axios库的拦截器使用

我们知道axios库的拦截器的使用方式如下:

  1. // 添加一个请求拦截器 
  2. axios.interceptors.request.use(function (config) { 
  3.   // 在发送请求之前可以做一些事情 
  4.   return config; 
  5. }, function (error) { 
  6.   // 处理请求错误 
  7.   return Promise.reject(error); 
  8. }); 
  9. // 添加一个响应拦截器 
  10. axios.interceptors.response.use(function (response) { 
  11.   // 处理响应数据 
  12.   return response; 
  13. }, function (error) { 
  14.   // 处理响应错误 
  15.   return Promise.reject(error); 
  16. }); 

axios对象上有一个interceptors对象属性,该属性又有requestresponse2 个属性,它们都有一个use方法,use方法支持 2 个参数,第一个参数类似 Promise.thenresolve函数,第二个参数类似 Promise.thenreject函数。我们可以在resolve函数和reject函数中执行同步代码或者是异步代码逻辑。

并且我们是可以添加多个拦截器的,拦截器的执行顺序是链式依次执行的方式。对于request拦截器,后添加的拦截器会在请求前的过程中先执行;对于response拦截器,先添加的拦截器会在响应后先执行。

  1. axios.interceptors.request.use(config => { 
  2.   config.headers.test += '1' 
  3.   return config 
  4. }) 
  5. axios.interceptors.request.use(config => { 
  6.   config.headers.test += '2' 
  7.   return config 
  8. }) 

此外,我们也可以支持删除某个拦截器,如下:

  1. const myInterceptor = axios.interceptors.request.use(function () {/*...*/}) 
  2. axios.interceptors.request.eject(myInterceptor) 

整体设计

我们先用一张图来展示一下拦截器工作流程:

整个过程是一个链式调用的方式,并且每个拦截器都可以支持同步和异步处理,我们自然而然地就联想到使用 Promise 链的方式来实现整个调用过程。

在这个 Promise 链的执行过程中,请求拦截器resolve函数处理的是config对象,而相应拦截器resolve函数处理的是response对象。

在了解了拦截器工作流程后,我们先要创建一个拦截器管理类,允许我们去添加 删除和遍历拦截器。

拦截器管理类实现

根据需求,axios拥有一个interceptors对象属性,该属性又有requestresponse2 个属性,它们对外提供一个use方法来添加拦截器,我们可以把这俩属性看做是一个拦截器管理对象。

use方法支持 2 个参数,第一个是resolve函数,第二个是reject函数,对于resolve函数的参数,请求拦截器是AxiosRequestConfig类型的,而响应拦截器是AxiosResponse类型的;而对于reject函数的参数类型则是any类型的。

根据上述分析,我们先来定义一下拦截器管理对象对外的接口。

接口定义

这里我们定义了AxiosInterceptorManager泛型接口,因为对于resolve函数的参数,请求拦截器和响应拦截器是不同的。

  1. export interface AxiosInterceptorManager<T> { 
  2.   use(resolved: ResolvedFn<T>, rejected?: RejectedFn): number 
  3.   eject(id: number): void 
  4. export interface ResolvedFn<T=any> { 
  5.   (val: T): T | Promise<T> 
  6. export interface RejectedFn { 
  7.   (error: any): any 

代码实现

  1. import { ResolvedFn, RejectedFn } from '../types' 
  2. interface Interceptor<T> { 
  3.   resolved: ResolvedFn<T> 
  4.   rejected?: RejectedFn 
  5. export default class InterceptorManager<T> { 
  6.   private interceptors: Array<Interceptor<T> | null
  7.   constructor() { 
  8. // 拦截器数组 
  9. this.interceptors = [] 
  10.   } 
  11.   // 收集拦截器   
  12.   use(resolved: ResolvedFn<T>, rejected?: RejectedFn): number { 
  13. this.interceptors.push({ 
  14.   resolved, 
  15.   rejected 
  16. }) 
  17. return this.interceptors.length - 1 
  18.   } 
  19.   // 遍历用户写的拦截器,并执行fn函数把拦截器作为参数传入 
  20.   forEach(fn: (interceptor: Interceptor<T>) => void): void { 
  21. this.interceptors.forEach(interceptor => { 
  22.   if (interceptor !== null) { 
  23. fn(interceptor) 
  24.   } 
  25. }) 
  26.   } 
  27.   eject(id: number): void { 
  28. if (this.interceptors[id]) { 
  29.   // 置为null,不能直接删除 
  30.   this.interceptors[id] = null 
  31.   } 

我们定义了一个InterceptorManager泛型类,内部维护了一个私有属性interceptors,它是一个数组,用来存储拦截器。该类还对外提供了 3 个方法,其中use接口就是添加拦截器到interceptors中,并返回一个id用于删除;

forEach接口就是遍历interceptors用的,它支持传入一个函数,遍历过程中会调用该函数,并把每一个interceptor作为该函数的参数传入;eject就是删除一个拦截器,通过传入拦截器的id删除。

链式调用实现

当我们实现好拦截器管理类,接下来就是在Axios中定义一个interceptors属性,它的类型如下:

  1. interface Interceptors { 
  2.   request: InterceptorManager<AxiosRequestConfig> 
  3.   response: InterceptorManager<AxiosResponse> 
  4. export default class Axios { 
  5.   interceptors: Interceptors 
  6.   constructor() { 
  7. this.interceptors = { 
  8.   request: new InterceptorManager<AxiosRequestConfig>(), 
  9.   response: new InterceptorManager<AxiosResponse>() 
  10.   } 

Interceptors类型拥有 2 个属性,一个请求拦截器管理类实例,一个是响应拦截器管理类实例。我们在实例化Axios类的时候,在它的构造器去初始化这个interceptors实例属性。

接下来,我们修改request方法的逻辑,添加拦截器链式调用的逻辑:

  1. interface PromiseChain { 
  2.   resolved: ResolvedFn | ((config: AxiosRequestConfig) => AxiosPromise) 
  3.   rejected?: RejectedFn 
  4. request(url: any, config?: any): AxiosPromise { 
  5.   if (typeof url === 'string') { 
  6. if (!config) { 
  7.   config = {} 
  8. config.url = url 
  9.   } else { 
  10. config = url 
  11.   } 
  12.   // 定义一个数组,这个数组就是要执行的任务链,默认有一个真正发送请求的任务 
  13.   const chain: PromiseChain[] = [{ 
  14. resolved: dispatchRequest, 
  15. rejected: undefined 
  16.   }] 
  17.   // 把用户定义的请求拦截器存放到任务链中,请求拦截器最后注册的最先执行,所以使用unshift方法 
  18.   this.interceptors.request.forEach(interceptor => { 
  19. chain.unshift(interceptor) 
  20.   }) 
  21.   // 把响应拦截器存放到任务链中 
  22.   this.interceptors.response.forEach(interceptor => { 
  23. chain.push(interceptor) 
  24.   }) 
  25.   // 利用config初始化一个promise 
  26.   let promise = Promise.resolve(config) 
  27.   // 遍历任务链 
  28.   while (chain.length) { 
  29. // 取出任务链的首个任务 
  30. const { resolved, rejected } = chain.shift()! 
  31. // resolved的执行时机是就是上一个promise执行resolve()的时候,这样就形成了链式调用 
  32. promise = promise.then(resolved, rejected) 
  33.   } 
  34.   return promise 

首先,构造一个PromiseChain类型的数组chain,并把dispatchRequest函数赋值给resolved属性;接着先遍历请求拦截器插入到chain的前面;然后再遍历响应拦截器插入到chain后面。

接下来定义一个已经 resolve 的promise,循环这个chain,拿到每个拦截器对象,把它们的resolved函数和rejected函数添加到promise.then的参数中,这样就相当于通过 Promise 的链式调用方式,实现了拦截器一层层的链式调用的效果。

注意我们拦截器的执行顺序,对于请求拦截器,先执行后添加的,再执行先添加的;而对于响应拦截器,先执行先添加的,后执行后添加的。

相关文章

站点信息