axios 二次封裝-攔截器隊列

查看axios的源碼,會發(fā)現(xiàn)攔截器是由簡單數(shù)組實現(xiàn),掛載use eject 方法。攔截器注冊的Id實際就是數(shù)組的編號,實現(xiàn)的核心在request調用前的攔截器隊列編排上。滿足平常開發(fā),如果想做擴展就有些限制,所以這里希望通過提供一個自定義的攔截器中間層。提供些擴展功能。

目標

  • 自定義標識id
  • 自定義請求端攔截器篩選
  • 執(zhí)行順序與綁定順序一致
  • 鏈式調用

使用

import { CandyPaper } from './src/core'
import { Interceptor } from './src/interceptor'
const http = new CandyPaper()
const task = {
  key: 'taskId',
  fulfilled: (ctx: AxiosRequestConfig) => {...},
  rejected: (ctx: AxiosResponse) => {...},
  runWhen: (ctx: AxiosRequestConfig) => true
}


// 注冊攔截器
http.interceptor.request
// 模式一
.use(
  task.fulfilled
)
// 模式二
.use(
  task.fulfilled,
  task.rejected
)
// 模式三
.use(
  task.key,
  taks.fulfilled,
  taks.rejected
)
// 模式四
.use( task )


// 注銷攔截器
 http.interceptor.request
 // 模式一
.eject(
  task.key
)
// 模式二
.eject(
  task.fulfilled
)


// 攔截器篩選


http.request(
  url: '/',
  // 忽略 'task' 攔截器
  $intercepteFilter: interceptor.Interceptor.excluedByKeys([
      task.key
  ])
)


http.request(
  url: '/',
  // 只執(zhí)行 'task' 攔截器
  $intercepteFilter: interceptor.Interceptor.incluedByKeys([
      task.key
  ])
)

實現(xiàn)

緩存隊列

因為需要自定義標識,同時需要保存注冊順序。 這里通過擴展Map類型實現(xiàn)


/**
 * map 擴展
 * @props list 鍵隊列
 * @fn $set 設置新值, 并緩存鍵
 * @fn $get 獲取值, 可接收key數(shù)組 
 * @fn $delete 刪除值, 并移除鍵
 * @fn $map 遍歷鍵
 * @fn $clear 清空
 */
export class Lmap<K, V> extends Map<K, V>{
  
  // key 添加記錄
  list: K[] = []
  
  constructor(entries?: readonly (readonly [K, V])[] | null){
    super(entries)
    entries && entries.map(([key]) => this.list.push(key))
  }


  $set(key: K, value: V): this {
    super.set(key, value)
    this.list.push(key)
    return this
  }
  
  $delete(key: K): boolean {
    if(!this.has(key)){
      return true
    }
    const status = super.delete(key)


    if(status){
      this.list = this.list.filter(k => k != key)
    }
    
    return status
  }


  $clear(){
    this.clear()
    this.list = []
  }


  $get(keys: K): V
  $get(keys: K[]): V[]
  $get(keys: K | K[]): (undefined | V | V[]) {


    if(!keys){
      return
    }
    
    if(!Array.isArray(keys)){
      return this.get(keys)
    }


    return keys.map(k => this.get(k)).filter(i => !!i) as V[]
  }




  $map<T = any>(cb:(k: K) => T): T[]
  $map<T = any>(cb:(k: K, v?: V) => T):T[] 
  $map<T = any>(cb: (k: K, v?:V) => T): T[]{
    return this.list.map(key => cb(key, this.get(key)))
  }
  
}

擴展aixos類型定義


// ./types/shime.aixos.d
import { AxiosRequestConfig, AxiosInterceptorManager } from 'axios'


declare module 'axios'{


  export interface AxiosRequestConfig{
    // 擴展請求配置參數(shù)
    $intercepteFilter?: (keys: IndexKey[]) => IndexKey[]
  }
  
  // 任務攔截
  interface Fulfilled<T = any>{
    (d: T): T | Promise<T>
    // 自定義標識
    key?: IndexKey
  }
  
  // 錯誤攔截
  interface Rejected{
    (err: any): any
    key?: IndexKey
  }
  
  // 攔截器端,篩選函數(shù)
  // @types/aixos未做定義,需要自己補充
  export type RunWhen = (conf:AxiosRequestConfig) => boolean | null
  
  // 攔截器項定義
  export interface InterceptorHandler<V>{
    key?: IndexKey
    fulfilled: Fulfilled<V>
    rejected?: Rejected
    runWhen?: RunWhen
  }
  
  // 擴展攔截器定義
  // @types/aixos未做定義,需要自己補充
  export interface AxiosInterceptorManager<V>{
    handlers: InterceptorHandler<V>[]
  }
}

攔截器中間層

import { 
  AxiosRequestConfig, 
  InterceptorHandler,
  Fulfilled,
  Rejected,
  RunWhen
} from 'axios'
import { Lmap } from './utils'
import  { is } from 'ramda'


interface  InterceptorItem <T = any> extends InterceptorHandler <T> {
  key: IndexKey
}




export interface InterceptorOptions {
  $intercepteFilter?: (keys: IndexKey[]) => IndexKey[]
}


/**
 * 攔截器
 */
export class Interceptor<T = AxiosRequestConfig>{


  // 攔截器隊列
  handlers: Lmap<IndexKey, InterceptorItem<T>> = new Lmap()


  // 默認攔截器id生成器
  createDefKey(){
    return new Date().getTime() + this.handlers.list.length
  }


  /**
   * 注冊攔截器
   * @param key 標識符
   * @param onFulfilled 任務函數(shù)
   * @param onRejected 錯誤捕獲
   * @exmple
   * 模式一
   * use('token',  setToken)
   * 
   * 模式二
   * use(setToken, onError)
   * 
   * 模式三
   * use('token', setToken, onError)
   * 
   * 模式四
   * use({
   *   key: 'token',
   *   onFulfilled: setToken,
   *   onRejected: onError
   *   runWhen: (ctx: AxiosRequestConfig) => {...}
   * })
   */


  use(key: Fulfilled<T>):Interceptor<T>
  use(key: InterceptorItem<T>): Interceptor<T>
  use(key: IndexKey, onFulfilled: Fulfilled<T>): Interceptor<T>
  use(key: Fulfilled<T>, onFulfilled: Rejected): Interceptor<T>
  use(key: IndexKey, onFulfilled: Fulfilled<T>, onRejected: Rejected): Interceptor<T>
  use(key?: IndexKey | Fulfilled<T> | InterceptorItem<T>,  onFulfilled?: Fulfilled, onRejected?: Rejected ){


    let runWhen: RunWhen | undefined
    
    if(!key){
      return this
    }


    if(is(Function, key)){ 
      onRejected = onFulfilled
      onFulfilled = key
      key = onFulfilled.key || this.createDefKey()
    }
    
    if(is(Object, key)){
      const options = key as InterceptorItem<T>
      key = options.key
      onFulfilled = options.fulfilled
      onRejected = options.rejected
      runWhen = options.runWhen
    }


    if(this.handlers.has(key)){
      throw new Error(`攔截器已注冊: ${String(key)}`)
    }


    if(onFulfilled){
      this.handlers.$set(key, {
        key,
        fulfilled: onFulfilled,
        rejected: onRejected,
        runWhen
      })


      onFulfilled.key = key
    }


    return this
  }




  /**
   * 注銷攔截器
   * @param key 注冊標識或注冊函數(shù)
   * @example
   * 模式一
   * const task = () => {}
   * interceptor.use(
   *   task
   * )
   * interceptor.eject(task)
   * 
   * 模式二
   * interceptor.use(
   *  'taskId',
   *  task
   * )
   * interceptor.eject(task)
   * or
   * interceptor.eject('taskId')
   */
  eject(key: IndexKey):void
  eject(key: Fulfilled):void
  eject(key: IndexKey | Fulfilled){
    let _k: IndexKey | undefined
    if(is(Function, key)){
        _k = (key as Fulfilled).key
    }else{
      _k = key
    }
    
    _k && this.handlers.$delete(_k)
    
  }


  // 清空攔截器隊列
  clean(){
    this.handlers.$clear()
  }


  // 構建任務隊列
  queupUp(ctx: InterceptorOptions){


    // 如果攔截器篩選為空, 則應用所有已注冊攔截器
    const filter =  ctx.$intercepteFilter ? ctx.$intercepteFilter : (keys: IndexKey[]) => keys


    // 篩選可用隊列
    return filter(this.handlers.list).reduce((acc, next) => {
      const handler = this.handlers.get(next)
      return handler ? [
        ...acc,
        {
          ...handler
        }
      ] : acc
    }, [] as InterceptorHandler<T>[])
   
  }


  // 預設排除篩選
  static excluedByKeys(exclueds: IndexKey[]){
    return (handlersKeys: IndexKey[]) => handlersKeys.filter(key => !exclueds.includes(key)) 
  }


  // 預設包含篩選
  static incluedByKeys(inclueds: IndexKey[]){
    return (handlersKeys: IndexKey[]) => handlersKeys.filter(key => inclueds.includes(key)) 
  }


}

請求包裝

import axios from 'axios'
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios'
import { Interceptor, InterceptorOptions } from './interceptor'
import { is } from 'ramda'




export class CandyPaper{
  
  candy: AxiosInstance


  interceptor = {
    request: new Interceptor<AxiosRequestConfig>(),
    response: new Interceptor<AxiosResponse>()
  }   


  /**
   * @param config axios實例 or axios配置對象
   * @example
   * 1. new CandyPaper({ baseUrl: '/' })
   * 2. new CandyPaper(axios.create({baseUrl: '/'}))
   */
  constructor(config?: AxiosInstance)
  constructor(config?: AxiosRequestConfig)
  constructor(config?: AxiosRequestConfig | AxiosInstance){
    this.candy =  is(Function, config) ? config : axios.create(config)
  }


  // 重組請求攔截器列表
  protected resetInterceptors(ctx: InterceptorOptions){
    const interceptorRequests = this.candy.interceptors.request.handlers.filter(i => !i.key)
    const interceptorResponses = this.candy.interceptors.response.handlers.filter(i => !i.key)


    const middleRequests  = this.interceptor.request.queupUp(ctx).sort(() => -1)
    this.candy.interceptors.request.handlers = [
      ...interceptorRequests,
      ...middleRequests
    ].sort(() => -1) // 反序, 保證執(zhí)行順序與注冊順序一致
    
    const middleResponses = this.interceptor.response.queupUp(ctx)
    this.candy.interceptors.response.handlers = [
      ...interceptorResponses,
      ...middleResponses
    ]
  }


  request(options: AxiosRequestConfig){
    // 請求前重置攔截器隊列
    this.resetInterceptors(options)
    return this.candy(options)
  } 
}

參考

axios 如何設計攔截器

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容