前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >axios 二次封装-拦截器队列

axios 二次封装-拦截器队列

作者头像
copy_left
发布2022-04-02 08:02:48
4490
发布2022-04-02 08:02:48
举报
文章被收录于专栏:方球方球

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

目标

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

使用

代码语言:javascript
复制
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类型实现

代码语言:javascript
复制
/**
 * map 扩展
 * @props list 键队列
 * @fn $set 设置新值, 并缓存键
 * @fn $get 获取值, 可接收key数组 
 * @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类型定义

代码语言:javascript
复制
// ./types/shime.aixos.d
import { AxiosRequestConfig, AxiosInterceptorManager } from 'axios'


declare module 'axios'{


  export interface AxiosRequestConfig{
    // 扩展请求配置参数
    $intercepteFilter?: (keys: IndexKey[]) => IndexKey[]
  }
  
  // 任务拦截
  interface Fulfilled<T = any>{
    (d: T): T | Promise<T>
    // 自定义标识
    key?: IndexKey
  }
  
  // 错误拦截
  interface Rejected{
    (err: any): any
    key?: IndexKey
  }
  
  // 拦截器端,筛选函数
  // @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>[]
  }
}

拦截器中间层

代码语言:javascript
复制
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 任务函数
   * @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 注册标识或注册函数
   * @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)) 
  }


}

请求包装

代码语言:javascript
复制
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) // 反序, 保证执行顺序与注册顺序一致
    
    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 如何设计拦截器

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022.03.27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目标
  • 使用
  • 实现
    • 缓存队列
      • 扩展aixos类型定义
        • 拦截器中间层
          • 请求包装
          • 参考
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档