美文网首页
5、拦截器的设计与实现

5、拦截器的设计与实现

作者: Eileen_1d88 | 来源:发表于2019-12-09 17:12 被阅读0次
    需求

    我们希望能对请求的发送和响应做拦截,也就是在发送请求之前和接收到响应之后做一些额外逻辑。我们希望设计的拦截器使用方式如下:

    axis.interceptors.request.use(function(config) {
      // do something
      return config
    }, function(error) {
      return Promise.reject(error)
    })
    
    axis.interceptors.response.use(function(response) {
      // do something
      return response
    }, function(error) {
      return Promise.reject(error)
    })
    

    在axios对象上有一个interceptors对象属性,该属性又有request和response 2个属性,他们都有一个use方法,use方法支持2个参数,第一个参数类似Promise的resolve函数,第二个函数类似Promise的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类型的。
    根据上述分析,我们先来定义一下拦截器管理对象对外的接口。

    接口定义
    interface AxiosInterceptorManager<T> {
      // 这里返回一个数字,作为interceptor的ID,后面做删除的时候会用到
      use(resolved: ResolveFn<T>, rejected?: RejectedFn): number
      eject(id: number): void
    }
    
    interface ResolveFn<T> {
      (val: T): T | Promise<T>
    }
    
    interface RejectedFn {
      (error: any): any
    }
    
    interface Interceptors {
      request: InterceptorManager<AxiosRequestConfig>
      response: InterceptorManager<AxiosResponse>
    }
    
    interceptorManager实现

    src/core/interceptorManager.ts

    import { ResolveFn, RejectedFn } from "../types";
    
    interface Interceptor<T> {
      resolved: ResolveFn<T>
      rejected?: RejectedFn
    }
    export default class InterceptorManager<T> {
      private interceptors: Array<Interceptor<T> | null>
      constructor() {
        this.interceptors = []
      }
      use(resolved: ResolveFn<T>, rejected?: RejectedFn): number {
        this.interceptors.push({
          resolved,
          rejected
        })
        return this.interceptors.length - 1
      }
      eject(id: number): void {
        if (this.interceptors[id]) {
          this.interceptors[id] = null
        }
      }
      forEach(fn: (Interceptor: Interceptor<T>) => void): void {
        this.interceptors.forEach(interceptor => {
          if (interceptor !== null) {
            fn(interceptor)
          }
        })
      }
    }
    
    interface Axios {
     interceptors: Interceptors
      request<T = any>(config: AxiosRequestConfig): AxiosPromise<T>
      get<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>
      head<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>
      delete<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>
      options<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>
      put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>
      post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>
      patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>
    }
    
    Axios 中增加interceptors
    import { AxiosRequestConfig, AxiosPromise, Method, AxiosResponse, ResolveFn, RejectedFn, Interceptors } from "../types";
    import dispatchRequest from "./dispatchRequest";
    import InterceptorManager from "./interceptorManager";
    
    interface PromiseChain {
      resolved: ResolveFn | ((config: AxiosRequestConfig) => AxiosPromise)
      rejected?: RejectedFn
    }
    export default class Axios {
      interceptors: Interceptors
      constructor() {
        this.interceptors = {
          request: new InterceptorManager<AxiosRequestConfig>(),
          response: new InterceptorManager<AxiosResponse>()
        }
      }
      request(url: string | AxiosRequestConfig, config?: AxiosRequestConfig): AxiosPromise {
        // 这里要定义一个新的newConfig变量,是因为AxiosPromise接收的泛型类型T是any,与config的类型不一致
        let newConfig: any
        newConfig = config || {}
        if (typeof url === 'string') {
          newConfig.url = url
        } else {
          newConfig = url
        }
        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(newConfig)
        while(chain.length) {
          const { resolved, rejected } = chain.shift()!
          promise = promise.then(resolved, rejected)
        }
        return promise
        // return dispatchRequest(config)
      }
      get(url: string, config?: AxiosRequestConfig): AxiosPromise {
        return this._requestMethod(url, 'get', config)
      }
      delete(url: string, config?: AxiosRequestConfig): AxiosPromise {
        // const newConfig = Object.assign(config || {}, {
        //   url,
        //   method: 'delete'
        // })
        // return dispatchRequest(newConfig)
        return this._requestMethod(url, 'delete', config)
      }
      head(url: string, config?: AxiosRequestConfig): AxiosPromise {
        return this._requestMethod(url, 'head', config)
      }
      options(url: string, config?: AxiosRequestConfig): AxiosPromise {
        return this._requestMethod(url, 'options', config)
      }
      put(url: string, data?: any, config?: AxiosRequestConfig,): AxiosPromise {
        return this._requestMethod(url, 'put', config, data)
      }
      post(url: string, data?: any, config?: AxiosRequestConfig,): AxiosPromise {
        return this._requestMethod(url, 'post', config, data)
      }
      patch(url: string, data?: any, config?: AxiosRequestConfig,): AxiosPromise {
        return this._requestMethod(url, 'patch', config, data)
      }
      _requestMethod(url: string, method: Method, config?: AxiosRequestConfig, data?: any) {
        const newConfig = Object.assign(config || {}, {
          url,
          method,
          data: data || null
        })
        return this.request(newConfig)
      }
    }
    

    这样,axios就拥有了interceptors属性,interceptors属性拥有use和eject方法,拦截器添加之后,每次请求时,都会经过请求拦截器、请求、响应拦截器。至此,我们完成了拦截器的功能,实际工作中,我们可以利用它做一下登陆认证之类的工作。

    我们目前通过ts-axios发送请求,往往会传入一堆配置,但是我们也希望ts-axios本身也会有一些默认配置,我们把用户传入的自定义配置和默认配置做一层合并。接下来我们来实现这个feature。

    相关文章

      网友评论

          本文标题:5、拦截器的设计与实现

          本文链接:https://www.haomeiwen.com/subject/oqhegctx.html