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

 更新时间:2022年8月27日 15:36  点击:1063 作者:小p

axios库的拦截器使用

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

// 添加一个请求拦截器
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.then 的 resolve 函数,第二个参数类似 Promise.then 的 reject 函数。我们可以在 resolve 函数和 reject 函数中执行同步代码或者是异步代码逻辑。

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

axios.interceptors.request.use(config => {
  config.headers.test += '1'
  return config
})
axios.interceptors.request.use(config => {
  config.headers.test += '2'
  return config
})

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

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

整体设计

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

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

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

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

拦截器管理类实现

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

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

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

接口定义

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

export interface AxiosInterceptorManager<T> {
  use(resolved: ResolvedFn<T>, rejected?: RejectedFn): number
  eject(id: number): void
}
export interface ResolvedFn<T=any> {
  (val: T): T | Promise<T>
}
export interface RejectedFn {
  (error: any): any
}

代码实现

import { ResolvedFn, RejectedFn } from '../types'
interface Interceptor<T> {
  resolved: ResolvedFn<T>
  rejected?: RejectedFn
}
export default class InterceptorManager<T> {
  private interceptors: Array<Interceptor<T> | null>
  constructor() {
    // 拦截器数组
    this.interceptors = []
  }
  // 收集拦截器  
  use(resolved: ResolvedFn<T>, rejected?: RejectedFn): number {
    this.interceptors.push({
      resolved,
      rejected
    })
    return this.interceptors.length - 1
  }
  // 遍历用户写的拦截器,并执行fn函数把拦截器作为参数传入
  forEach(fn: (interceptor: Interceptor<T>) => void): void {
    this.interceptors.forEach(interceptor => {
      if (interceptor !== null) {
        fn(interceptor)
      }
    })
  }
  eject(id: number): void {
    if (this.interceptors[id]) {
      // 置为null,不能直接删除
      this.interceptors[id] = null
    }
  }
}

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

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

链式调用实现

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

interface Interceptors {
  request: InterceptorManager<AxiosRequestConfig>
  response: InterceptorManager<AxiosResponse>
}
export default class Axios {
  interceptors: Interceptors
  constructor() {
    this.interceptors = {
      request: new InterceptorManager<AxiosRequestConfig>(),
      response: new InterceptorManager<AxiosResponse>()
    }
  }
}

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

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

interface PromiseChain {
  resolved: ResolvedFn | ((config: AxiosRequestConfig) => AxiosPromise)
  rejected?: RejectedFn
}
request(url: any, config?: any): AxiosPromise {
  if (typeof url === 'string') {
    if (!config) {
      config = {}
    }
    config.url = url
  } else {
    config = url
  }
  // 定义一个数组,这个数组就是要执行的任务链,默认有一个真正发送请求的任务
  const chain: PromiseChain[] = [{
    resolved: dispatchRequest,
    rejected: undefined
  }]
  // 把用户定义的请求拦截器存放到任务链中,请求拦截器最后注册的最先执行,所以使用unshift方法
  this.interceptors.request.forEach(interceptor => {
    chain.unshift(interceptor)
  })
  // 把响应拦截器存放到任务链中
  this.interceptors.response.forEach(interceptor => {
    chain.push(interceptor)
  })
  // 利用config初始化一个promise
  let promise = Promise.resolve(config)
  // 遍历任务链
  while (chain.length) {
    // 取出任务链的首个任务
    const { resolved, rejected } = chain.shift()!
    // resolved的执行时机是就是上一个promise执行resolve()的时候,这样就形成了链式调用
    promise = promise.then(resolved, rejected)
  }
  return promise
}

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

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

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

以上就是axios 拦截器管理类链式调用手写实现及原理剖析的详细内容,更多关于axios 拦截器管理类链式调用的资料请关注猪先飞其它相关文章!

原文出处:https://juejin.cn/post/7136065256941420552

[!--infotagslink--]

相关文章

  • vue+axios全局添加请求头和参数操作

    这篇文章主要介绍了vue+axios全局添加请求头和参数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-24
  • 封装 axios+promise通用请求函数操作

    这篇文章主要介绍了封装 axios+promise通用请求函数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-12
  • Vue中 axios delete请求参数操作

    这篇文章主要介绍了Vue中 axios delete请求参数操作,具有很好的参考价值,希望对大家有所 帮助。一起跟随小编过来看看吧...2020-08-26
  • 快速学习AngularJs HTTP响应拦截器

    任何时候,如果我们想要为请求添加全局功能,例如身份认证、错误处理等,在请求发送给服务器之前或服务器返回时对其进行拦截,是比较好的实现手段...2016-01-05
  • springboot配置多数据源后mybatis拦截器失效的解决

    这篇文章主要介绍了springboot配置多数据源后mybatis拦截器失效的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-23
  • Vue使用axios引起的后台session不同操作

    这篇文章主要介绍了Vue使用axios引起的后台session不同操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-14
  • 如何用vue封装axios请求

    对axios进行封装以及将API接口按业务模块统一管理,有助于我们简化代码,方便后期维护。本文介绍了如何用vue封装axios请求,感兴趣的同学,可以参考下。...2021-06-04
  • Vue3配置axios跨域实现过程解析

    这篇文章主要介绍了Vue3配置axios跨域实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-11-25
  • 项目中Axios二次封装实例Demo

    vue项目经常会用到axios来请求数据,那么首先肯定需要对这个请求方法进行一个二次封装,这篇文章主要给大家介绍了关于项目中Axios二次封装的相关资料,需要的朋友可以参考下...2021-06-08
  • 深入分析C#键盘勾子(Hook)拦截器,屏蔽键盘活动的详解

    本篇文章是对C#键盘勾子(Hook)拦截器,屏蔽键盘活动进行了详细的分析介绍,需要的朋友参考下...2020-06-25
  • Vue项目中如何封装axios(统一管理http请求)

    这篇文章主要给大家介绍了关于Vue项目中如何封装axios(统一管理http请求)的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-05-02
  • 使用spring拦截器实现日志管理实例

    本文介绍了使用spring拦截器实现日志管理实例的教程,非常实用,有兴趣的同学可以参考一下本文 使用HandlerInterceptor拦截器,可以拦截请求,实现通用的日志管理操作 一...2017-07-06
  • React开启代理的2种实用方式

    最近有不少伙伴询问react的代理配置,自己也去试验了一下发现不少的问题,在这就将所遇到的心得分享出来,这篇文章主要给大家介绍了关于React开启代理的2种实用方式的相关资料,需要的朋友可以参考下...2021-07-29
  • SpringBoot之HandlerInterceptor拦截器的使用详解

    这篇文章主要介绍了SpringBoot之HandlerInterceptor拦截器的使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-26
  • axios封装与传参示例详解

    这篇文章主要给大家介绍了关于axios封装与传参的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-18
  • 在项目中封装axios的实战过程

    这篇文章主要给大家介绍了关于如何在项目中封装axios的相关资料,axios 请求的封装,无非是为了方便代码管理,文中通过示例代码介绍的非常详细,需要的朋友可以参考下...2021-09-20
  • vue中axios封装使用的完整教程

    这篇文章主要给大家介绍了关于vue中axios封装使用的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-03
  • 使用Spring的拦截器监测每个Controller或方法的执行时长

    这篇文章主要介绍了使用Spring的拦截器监测每个Controller或方法的执行时长,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-28
  • Angularjs注入拦截器实现Loading效果

    angularjs作为一个全ajax的框架,对于请求,如果页面上不做任何操作的话,在结果反回来之前,页面是没有任何响应的,不像普通的HTTP请求,会有进度条之类...2015-12-29
  • vue中封装axios并实现api接口的统一管理

    这篇文章主要介绍了vue中封装axios并实现api接口的统一管理的方法,帮助大家更好的理解和使用vue,感兴趣的朋友可以了解下...2020-12-25