axios 攔截器管理類鏈式調用手寫實現及原理剖析

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 攔截器管理類鏈式調用的資料請關註WalkonNet其它相關文章!

推薦閱讀: