复盘! Axios的二次封装

169 阅读3分钟

背景

随着项目规模增大,如果每发起一次HTTP请求,就要把这些比如设置超时时间、设置请求头、根据项目环境判断使用哪个请求地址、错误处理等等操作,都需要写一遍,这种重复劳动不仅浪费时间,而且让代码变得冗余不堪,难以维护。总的来说,对 axios 进行二次封装有如下好处:

  • 代码封装,重用性高,减少代码量,减少维护难度
  • 统一处理一些常规的问题,一劳永逸,比如 HTTP 错误
  • 拦截请求和响应,提前对数据进行处理,如获取 TOKEN,修改配置项

封装要考虑的问题

  • 根据开发、测试、生产环境的不同,接口请求前缀需要加以区分
  • 请求之前处理 config
  • 根据接口返回的不同状态码做不同的处理
  • 对 Get、Post 等方法进行封装,使用起来更方便
  • 针对文件上传封装统一的请求方法
  • 在响应拦截器中进行错误捕获
  • 具备取消重复请求、错误请求重连的功能
  • 对接口返回的数据进行处理,封装消息提示方法

重点细节复盘

1、重复请求问题

export class AxiosCanceler {
  /**
   * 添加请求
   * @param {Object} config
   */
  addPending(config: AxiosRequestConfig) {
    this.removePending(config);
    const url = getPendingUrl(config);
    config.cancelToken =
      config.cancelToken ||
      new axios.CancelToken((cancel) => {
        if (!pendingMap.has(url)) {
          // 如果 pending 中不存在当前请求,则添加进去
          pendingMap.set(url, cancel);
        }
      });
  }

  /**
   * @description: 清空所有pending
   */
  removeAllPending() {
    pendingMap.forEach((cancel) => {
      cancel && isFunction(cancel) && cancel();
    });
    pendingMap.clear();
  }

  /**
   * 移除请求
   * @param {Object} config
   */
  removePending(config: AxiosRequestConfig) {
    const url = getPendingUrl(config);

    if (pendingMap.has(url)) {
      // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
      const cancel = pendingMap.get(url);
      cancel && cancel(url);
      pendingMap.delete(url);
    }
  }

  /**
   * @description: 重置
   */
  reset(): void {
    pendingMap = new Map<string, Canceler>();
  }


从 v0.22.0 开始,Axios 支持以 fetch API 方式—— AbortController 取消请求 取消请求 | Axios Docs

小结

  • 为每个请求生成唯一标识:通过 getPendingUrl 确保每个请求可以唯一标识。
  • 添加请求到 pendingMap
  • 在请求发起前,生成取消令牌并存入 pendingMap
  • 移除已存在的重复请求:在 addPending 中调用 removePending,确保当前的请求是最新的,避免重复发送。
  • 支持取消所有请求:提供 removeAllPending 方法,便于全局清理。
  • 重置请求管理器:通过 reset 方法清空所有记录。

2、在 axios 实例类中封装请求方法(Get、Post、Put、Delete) 好处:不用写重复代码,只需要维护对应的api路径

  get<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: 'GET' }, options);
  }

  post<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: 'POST' }, options);
  }

  put<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: 'PUT' }, options);
  }

  delete<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: 'DELETE' }, options);
  }

设计模式

  1. 单例模式 (Singleton Pattern)

    pendingMap 是一个全局唯一的实例,用于存储和管理所有的请求标识和取消函数。通过单例模式,确保请求管理器在整个应用中只有一个统一的数据管理对象。

  2. 策略模式 (Strategy Pattern)

    AxiosCanceler 类中的方法(如 addPendingremovePendingremoveAllPending)可以视为不同的策略,用于管理请求的生命周期。根据需求,调用不同的方法实现对应的功能:

    • addPending:用于添加新的请求。
    • removePending:用于移除指定请求。
    • removeAllPending:用于清空所有请求。
    • reset:重置请求管理器。

参数的加密解密

base64加密正常是字符串(String类型)进行加密,对对象(Object)进行加密,需要通过JSON.parse进行转义成String类型

/**
 * base64(解密)
 * @param {String} str 跳转参数为base64字符串
 * @returns
 */
export const decryptBase64 = function(str) {
  // 添加decodeURIComponent解决其他特殊字符,如等号(=)会转成%3D,导致base64解密失败
  const decryptQuery = str ? JSON.parse(base64.decode(decodeURIComponent(str))) : {}
  return decryptQuery
}

/**
 * base64(加密)
 * @param {Object || String} param 跳转参数可以为对象或路径字符串
 * @returns
 */
export const encryptBase64 = function(param) {
  const encryptStr = base64.encode(JSON.stringify(param)) || ''
  return encryptStr
}