5、拦截器的设计与实现

需求

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

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 {
  // 这里返回一个数字,作为interceptor的ID,后面做删除的时候会用到
  use(resolved: ResolveFn, rejected?: RejectedFn): number
  eject(id: number): void
}

interface ResolveFn {
  (val: T): T | Promise
}

interface RejectedFn {
  (error: any): any
}

interface Interceptors {
  request: InterceptorManager
  response: InterceptorManager
}
interceptorManager实现

src/core/interceptorManager.ts

import { ResolveFn, RejectedFn } from "../types";

interface Interceptor {
  resolved: ResolveFn
  rejected?: RejectedFn
}
export default class InterceptorManager {
  private interceptors: Array | null>
  constructor() {
    this.interceptors = []
  }
  use(resolved: ResolveFn, 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) => void): void {
    this.interceptors.forEach(interceptor => {
      if (interceptor !== null) {
        fn(interceptor)
      }
    })
  }
}
interface Axios {
 interceptors: Interceptors
  request(config: AxiosRequestConfig): AxiosPromise
  get(url: string, config?: AxiosRequestConfig): AxiosPromise
  head(url: string, config?: AxiosRequestConfig): AxiosPromise
  delete(url: string, config?: AxiosRequestConfig): AxiosPromise
  options(url: string, config?: AxiosRequestConfig): AxiosPromise
  put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
  post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
  patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
}
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(),
      response: new InterceptorManager()
    }
  }
  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、拦截器的设计与实现)