axios 二次封装-拦截器队列

查看axios的源码,会发现拦截器是由简单数组实现,挂载use eject 方法。拦截器注册的Id实际就是数组的编号,实现的核心在request调用前的拦截器队列编排上。满足平常开发,如果想做扩展就有些限制,所以这里希望通过提供一个自定义的拦截器中间层。提供些扩展功能。

目标

  • 自定义标识id
  • 自定义请求端拦截器筛选
  • 执行顺序与绑定顺序一致
  • 链式调用

使用

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: '/',
  // 只执行 'task' 拦截器
  $intercepteFilter: interceptor.Interceptor.incluedByKeys([
      task.key
  ])
)

实现

缓存队列

因为需要自定义标识,同时需要保存注册顺序。 这里通过扩展Map类型实现


/**
 * map 扩展
 * @props list 键队列
 * @fn $set 设置新值, 并缓存键
 * @fn $get 获取值, 可接收key数组 
 * @fn $delete 删除值, 并移除键
 * @fn $map 遍历键
 * @fn $clear 清空
 */
export class Lmap extends Map{
  
  // 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(cb:(k: K) => T): T[]
  $map(cb:(k: K, v?: V) => T):T[] 
  $map(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{
    // 扩展请求配置参数
    $intercepteFilter?: (keys: IndexKey[]) => IndexKey[]
  }
  
  // 任务拦截
  interface Fulfilled{
    (d: T): T | Promise
    // 自定义标识
    key?: IndexKey
  }
  
  // 错误拦截
  interface Rejected{
    (err: any): any
    key?: IndexKey
  }
  
  // 拦截器端,筛选函数
  // @types/aixos未做定义,需要自己补充
  export type RunWhen = (conf:AxiosRequestConfig) => boolean | null
  
  // 拦截器项定义
  export interface InterceptorHandler{
    key?: IndexKey
    fulfilled: Fulfilled
    rejected?: Rejected
    runWhen?: RunWhen
  }
  
  // 扩展拦截器定义
  // @types/aixos未做定义,需要自己补充
  export interface AxiosInterceptorManager{
    handlers: InterceptorHandler[]
  }
}

拦截器中间层

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


interface  InterceptorItem  extends InterceptorHandler  {
  key: IndexKey
}




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


/**
 * 拦截器
 */
export class Interceptor{


  // 拦截器队列
  handlers: Lmap> = new Lmap()


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


  /**
   * 注册拦截器
   * @param key 标识符
   * @param onFulfilled 任务函数
   * @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):Interceptor
  use(key: InterceptorItem): Interceptor
  use(key: IndexKey, onFulfilled: Fulfilled): Interceptor
  use(key: Fulfilled, onFulfilled: Rejected): Interceptor
  use(key: IndexKey, onFulfilled: Fulfilled, onRejected: Rejected): Interceptor
  use(key?: IndexKey | Fulfilled | InterceptorItem,  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
      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 注册标识或注册函数
   * @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[])
   
  }


  // 预设排除筛选
  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(),
    response: new Interceptor()
  }   


  /**
   * @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) // 反序, 保证执行顺序与注册顺序一致
    
    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 如何设计拦截器

你可能感兴趣的:(axios 二次封装-拦截器队列)