乐趣区

关于axios:axios增强版封装

概述

axios 库自身曾经很好应用了。然而具体到业务层面,会波及到几个十分高频触发的情景须要提取解决。最罕用的可能如下:

  1. 勾销反复的申请。(频繁操作或者 state 频繁更新导致组件频繁 render 触发的多次重复申请)
  2. 失败主动发动重试。(因为网络稳定或者服务器不稳固起因,重发可进步成功率的状况)
  3. 主动缓存申请后果。(对于实时性要求不高的接口,能够缓存后果,防止申请)

思路实现

勾销

对于 axios 的勾销机制原理,有一篇文章剖析过了,有趣味的能够跳转查看 axios 的 cancelToken 勾销机制原理

剖析:

  1. 首先要对立治理多个申请,所以比拟容易想到 应用一个队列来治理申请,而后思考到每个申请的唯一性,就天然会想到 Map 数据结构。
  2. Map 申请治理数据结构增加和删除的机会,天然是利用 axios 提供的申请 / 响应拦截器来解决。
  3. 而后每次开始申请和完结申请的时候,检测 Map 数据申请构造,如果有进行清理。

重试

重试 retry 的话,在上述根底上,在响应拦截器里监听如果 error 失败,那么再次发动申请即可

缓存

cache 性能应该是最简略的,只须要在 响应拦截器里缓存 response,而后在每次开始申请的时候,检测是否有缓存的 response,如果有,则间接返回即可。

技术实现

主动勾销

先实现主动勾销性能,根据上述剖析,咱们首先须要一个 Map 数据结构来存储申请。代码如下:

class MAxios {private requestMap = new Map();
}

而后咱们须要一个对外的启动申请的接口函数,承受一些配置项参数。
在此函数里,咱们须要实例化 axios 申请,为了方便管理,使每个申请间的配置互不烦扰,咱们采纳一个申请一个实例的治理。
此外还须要对每个申请增加 申请、响应拦截器。
伪代码如下:

class MAxios {private requestMap = new Map();
  
  
  private interceptorsRequest(instance: AxiosInstance) {}
  private interceptorsResponse(instance: AxiosInstance) {}
  
  private interceptors(instance: AxiosInstance) {this.interceptorsRequest(instance);
    this.interceptorsResponse(instance);
  }
  
   /**
   * 实例工厂函数
   */
  private getAxiosInstance(config: IConfig) {const instance = axios.create();
    return instance;
  }

  
  public request(options: IConfig) {const config = Object.assign({}, defaultConfig, options);
    // 工厂函数获取 axios 实例
    const instance = this.getAxiosInstance();
    // 增加拦截器
    this.interceptors(instance);
    // 返回申请
    return instance.request(config);
  }
}

接下来咱们着重实现 拦截器细节。对申请拦截器和响应拦截器别离剖析。

  • 申请拦截器
    咱们须要首先去 Map 构造里查看是否有缓存,有的话移除,而后增加新的
  • 响应拦截器
    申请胜利或者失败之后,都须要移除 Map 缓存的申请

Map 缓存的 key,能够提供个接口给开发者自定义或者内置一个默认策略,比方依据 method 和 url 来生成。
参考代码:

 /**
   * 申请拦截器
   * @param {*} instance
   */
  private interceptorsRequest(instance: AxiosInstance) {
    instance.interceptors.request.use((config) => {this.removeReq(config);
        this.addReq(config);
        return config;
      },
      (error) => {
        // Do something with request error
        return Promise.reject(error);
      }
    );
  }
  /**
   * 响应拦截器
   * @param {*} instance
   */
  private interceptorsResponse(instance: AxiosInstance) {
    instance.interceptors.response.use((response) => {this.removeReq(response.config);
        return response;
      },
      (error) => {const config = error.config || {};
        this.removeReq(config);
        if (axios.isCancel(error)) {return Promise.reject(error.message);
        } else {return Promise.reject(error);
        }
      }
    );
  }
  
   /**
   * 生成标识申请的惟一 key
   */
  getDuplicatedKey(config) {const { duplicatedKey = () => "" } = config;
    return duplicatedKey(config);
  }
  
 /**
   * 增加申请
   */
  addReq(config) {const { cancelDuplicated} = config;
    if (!cancelDuplicated) {return;}
    const key = this.getDuplicatedKey(config);
    if (!this.requestMap.has(key)) {config.cancelToken = new axios.CancelToken((c) => {this.requestMap.set(key, c);
      });
    }
  }
  /**
   * 移除申请
   */
  removeReq(config) {
    try {const { cancelDuplicated} = config;
      const key = this.getDuplicatedKey(config);
      if (!cancelDuplicated) {return;}
      if (!this.requestMap.has(key)) {return;}
      const cancel = this.requestMap.get(key);
      this.requestMap.delete(key);
      cancel({
        type: ERROR_TYPE.Cancel,
        message: "Request canceled",
      });
    } catch (error) {console.log("removeReq:", error);
    }
  }

到这里勾销申请的性能就实现了。

主动重试

重试的逻辑非常简单,只须要在失败的时候判断一下重试次数,而后依据条件再次发动申请即可,如下:

   /**
   * 响应拦截器
   * @param {*} instance
   */
  private interceptorsResponse(instance: AxiosInstance) {
    instance.interceptors.response.use((response) => {//balabala},
      (error) => {
          // retry 重试逻辑
          if (config.retry > 0) {return this.retry({ instance, config, error});
          }
          return Promise.reject(error);
        }
      }
    );
  }
 
/**
   * 重试某次申请
   */
  private retry({
    instance,
    config,
    error
  }: {
    instance: AxiosInstance;
    config: IConfig;
    error: AxiosError;
  }) {const { retry, retryDelay, retryDelayRise} = config;
    let retryCount = config.__retryCount || 0;
    config.__retryCount = retryCount;
    // 查看是否超过重置次数
    if (retryCount >= retry!) {return Promise.reject(error);
    }
    // 重发计数器加 1
    retryCount += 1;
    config.__retryCount = retryCount;

    if (retryCount === retry) {config.timeout = 15000;}
    // 延时重发
    let delay = 0;
    if (typeof retryDelay === 'number') {delay = retryDelay * (retryDelayRise ? retryCount : 1);
    } else {delay = retryDelay!(retryCount);
    }

    const retryTask = new Promise<void>((resolve) => {setTimeout(() => {resolve();
      }, delay);
    });
    return retryTask.then(() => {return instance.request(config);
    });
  }

OK,是不是非常简单?

缓存后果

缓存是最简略的性能了,间接贴代码:

  private responseCacheMap = new Map();
 /**
   * 获取 response 缓存
   * @param {*} config
   */
  private getResponseCache(config: IConfig) {const key = this.getDuplicatedKey(config);
    if (this.responseCacheMap.has(key)) {return this.responseCacheMap.get(key);
    }
    return null;
  }
  /**
   * 设置 response 缓存
   * @param {*} config
   * @param {*} response
   */
  private setResponseCache(config: IConfig, response: AxiosResponse) {if (!config.cache) {return;}
    const key = this.getDuplicatedKey(config);
    this.responseCacheMap.set(key, response);
  }

  public request(options: IConfig) {const config = Object.assign({}, defaultConfig, options);
    if (config.cache) {const responseCache = this.getResponseCache(config);
      if (responseCache) {return Promise.resolve(responseCache);
      }
    }
    // balabala
  }

OK,GameOver!

上面贴上残缺代码:

import axios, {AxiosRequestConfig, Method, AxiosInstance, AxiosResponse, AxiosError} from 'axios';

type IRetryDelay = number | ((c: number) => number);

interface IConfig extends AxiosRequestConfig {
  cancelDuplicated?: boolean;
  duplicatedKey?: (ops: IConfig) => string;
  retry?: number;
  retryDelay?: IRetryDelay;
  retryDelayRise?: boolean;
  cache?: boolean;
  __retryCount?: number;
}

const defaultConfig: IConfig = {
  method: 'get',
  cancelDuplicated: false,
  duplicatedKey: ({method, url}) => `${(method as Method).toLocaleLowerCase()}${url}`,
  retry: 0,
  retryDelay: 200,
  retryDelayRise: true,
  cache: false
};

const ERROR_TYPE = {Cancel: 'cancelDuplicated'};

class MAxios {
  public name: string = 'MAxios';
  private requestMap = new Map();
  private responseCacheMap = new Map();

  /**
   * 生成标识申请的惟一 key
   */
  private getDuplicatedKey(config: IConfig) {const { duplicatedKey = () => '' } = config;
    return duplicatedKey(config);
  }
  /**
   * 增加申请
   */
  private addReq(config: IConfig) {const { cancelDuplicated} = config;
    if (!cancelDuplicated) {return;}
    const key = this.getDuplicatedKey(config);
    if (!this.requestMap.has(key)) {config.cancelToken = new axios.CancelToken((c) => {this.requestMap.set(key, c);
      });
    }
  }
  /**
   * 移除申请
   */
  private removeReq(config: IConfig) {
    try {const { cancelDuplicated} = config;
      const key = this.getDuplicatedKey(config);
      if (!cancelDuplicated) {return;}
      if (!this.requestMap.has(key)) {return;}
      const cancel = this.requestMap.get(key);
      this.requestMap.delete(key);
      cancel({
        type: ERROR_TYPE.Cancel,
        message: 'Request canceled'
      });
    } catch (error) {console.log('removeReq:', error);
    }
  }

  /**
   * 重试某次申请
   */
  private retry({
    instance,
    config,
    error
  }: {
    instance: AxiosInstance;
    config: IConfig;
    error: AxiosError;
  }) {const { retry, retryDelay, retryDelayRise} = config;
    let retryCount = config.__retryCount || 0;
    config.__retryCount = retryCount;
    // 查看是否超过重置次数
    if (retryCount >= retry!) {return Promise.reject(error);
    }
    // 重发计数器加 1
    retryCount += 1;
    config.__retryCount = retryCount;

    if (retryCount === retry) {config.timeout = 15000;}
    // 延时重发
    let delay = 0;
    if (typeof retryDelay === 'number') {delay = retryDelay * (retryDelayRise ? retryCount : 1);
    } else {delay = retryDelay!(retryCount);
    }

    const retryTask = new Promise<void>((resolve) => {setTimeout(() => {resolve();
      }, delay);
    });
    return retryTask.then(() => {return instance.request(config);
    });
  }
  /**
   * 获取 response 缓存
   * @param {*} config
   */
  private getResponseCache(config: IConfig) {const key = this.getDuplicatedKey(config);
    if (this.responseCacheMap.has(key)) {return this.responseCacheMap.get(key);
    }
    return null;
  }
  /**
   * 设置 response 缓存
   * @param {*} config
   * @param {*} response
   */
  private setResponseCache(config: IConfig, response: AxiosResponse) {if (!config.cache) {return;}
    const key = this.getDuplicatedKey(config);
    this.responseCacheMap.set(key, response);
  }

  /**
   * 实例工厂函数
   */
  private getAxiosInstance(config: IConfig) {const instance = axios.create();
    return instance;
  }
  /**
   * 申请拦截器
   * @param {*} instance
   */
  private interceptorsRequest(instance: AxiosInstance) {
    instance.interceptors.request.use((config) => {
        // Do something before request is sent
        this.removeReq(config);
        this.addReq(config);
        return config;
      },
      (error) => {
        // Do something with request error
        return Promise.reject(error);
      }
    );
  }
  /**
   * 响应拦截器
   * @param {*} instance
   */
  private interceptorsResponse(instance: AxiosInstance) {
    instance.interceptors.response.use((response) => {
        // Do something with response data with status code 2xx
        this.removeReq(response.config);
        this.setResponseCache(response.config, response);
        return response;
      },
      (error) => {const config = error.config || {};
        this.removeReq(config);

        if (axios.isCancel(error)) {return Promise.reject(error.message);
        } else {
          // retry 重试逻辑
          if (config.retry > 0) {return this.retry({ instance, config, error});
          }
          return Promise.reject(error);
        }
      }
    );
  }

  private interceptors(instance: AxiosInstance) {this.interceptorsRequest(instance);
    this.interceptorsResponse(instance);
  }

  /**
   * public request 对外接口
   * @param {*} config 以后申请的配置参数
   */
  public request(options: IConfig) {const config = Object.assign({}, defaultConfig, options);

    if (config.cache) {const responseCache = this.getResponseCache(config);
      if (responseCache) {return Promise.resolve(responseCache);
      }
    }

    const instance = this.getAxiosInstance(config);
    this.interceptors(instance);
    return instance.request(config);
  }
}

export default new MAxios();

源码获取

想间接下载应用源代码的,能够跳转 github 仓库,蕴含 javascript 和 Typescript 两个版本的。
源代码获取

退出移动版