axios异步时序处理方案(二)

792 阅读2分钟

1.前言

近期项目里的联想组件,与后端建立网络连接,为了保证弱网环境下的时序问题,使用的方案是axios提供的取消请求方案。可移步。因此在多次请求下,虽然前端做了防抖,但在弱网环境下,还是会在控制台会暴一堆红(取消请求),显得十分不优雅。并且后端同学反馈,因为建立的连接还没响应就被我们杀死了,会导致后端暴异常。

那么有没有更加优雅的方案?此处省略...好多文字。当然有!

2.方案思路

在请求中加一把锁,把同一个url的请求存在一个队列里,如果上一次请求还没完事,也就是锁还没开启时,我们将后续的请求存储在队列里,不触发。等请求完事后开锁,不就搞定了。可能有人会问,既然都不触发,那么还存个锤子的队列,直接return不就好了。这里有个问题,如果后面的请求参数和当前未结束的请求的参数不一致,那么就要发起最后一次请求保证最后一次响应的数据是用户需要的,这时这个队列就起作用啦。

3.主要数据结构

/** 请求锁
  * 判断当前队列是否存在请求未结束的 */
isRequesting: { [key: string]: boolean } = {};

/** 请求队列
   * key-唯一标识
   */
requestList: {
    [key: string]: IRequest[];
  } = {};

可以用请求路径(url)作为唯一标识。

4.主要代码

instance: AxiosInstance;

 /** 请求队列
   * key-唯一标识
 */
  requestList: {
    [key: string]: IRequest[];
  } = {};

  /** 请求锁 */
  isRequesting: { [key: string]: boolean } = {};

  constructor(defaultConfig: IAxiosRequestConfig = {}) {
    this.instance = axios.create(defaultConfig);
  }

  /** 更新回调函数数组 */
  setRequestList = (key: string, params: IRequest) => {
    this.requestList = {
      [key]: this.requestList[key] ? [...this.requestList[key], params] : [params],
    };
  };

  /** 处理成功请求
   * first-首次请求 last-最后一次请求 key-请求路径
   */
  handleSuccess = ({ key, resolve, res: firstRes }: { key: string; resolve: any; res: any }) => {
    this.isRequesting[key] = false;
    const requestLen = this.requestList[key]?.length || 0;
    if (requestLen >= 2) {
      const first = this.requestList[key][0];
      const last = this.requestList[key][requestLen - 1];
      if (!_.isEqual(first.config, last.config)) {
        // @ts-ignore
        last.method<T>(last.config).then((lastRes: AxiosResponse<any>) => {
          resolve(lastRes?.data);
        });
      } else {
        resolve(firstRes.data);
      }
    } else {
      resolve(firstRes.data);
    }
    this.requestList[key] = [];
  };

  /**
   * 当多次发起请求并且第一次请求未结束时,若参数完全一致只允许首次请求,不一致在首次结束后,进行最后一次
   */
  requestInterception<T>(config: IAxiosRequestConfig): Promise<T> {
    const sentryParams = {
      data: config.data,
      params: config.params,
      url: config.url,
    };
    const requestMethod = this.instance.request;
    const key: string = config.url || '';
    return new Promise((resolve, reject) => {
      if (this.isRequesting[key]) {
        this.setRequestList(key, { method: requestMethod, config });
      } else {
        this.isRequesting[key] = true;
        this.setRequestList(key, { method: requestMethod, config });
        requestMethod<T>(config)
          .then((res: AxiosResponse<any>) => {
            const { code, msg = '' } = res.data;
            this.handleSuccess({
              res,
              key,
              resolve,
            });
          })
          .catch((e) => {
            reject(e);
          });
      }
    });
  }

当然,这种方案不是适合所有场景,可能有些场景下就是允许,同样的请求存在。所以我们可以在axios的config加个自定义参数,当明确需要的时候,在走这种方案 否则就执行正常的请求方式,即可。

5.写在最后

当然可能这并不是最佳的方案,目前在我们项目生产环境运行一段时间,暂未有不好的反馈。有兴趣的可以试试。亲测好使。。。