您现在的位置是:首页 > 编程语言学习 > 后端编程语言 > 文章正文 后端编程语言
axios拦截器管理类链式调用手写实现及原理剖析
2022-08-29 10:18:45
后端编程语言
简介axios库的拦截器使用我们知道axios库的拦截器的使用方式如下://添加一个请求拦截器axios.interceptors.request.use(function(config){//在...
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 的链式调用方式,实现了拦截器一层层的链式调用的效果。
注意我们拦截器的执行顺序,对于请求拦截器,先执行后添加的,再执行先添加的;而对于响应拦截器,先执行先添加的,后执行后添加的。
上一篇:lodash里to系列之将数据转换成数字类型实现示例
下一篇:最后一页