前端混子之重复请求处理

128 阅读2分钟

前言

混子来写掘金了,大佬轻点喷

业务场景

同个页面可能因为业务的复杂度与组件的重复调用导致相同接口重复调用,闲着无聊优化一下

微信截图_20240125202919.png

实现

用Axios进行的一个封装,正常请求就不多说了,主要针对重复请求的情况

实现思路

Axios有两个拦截器,一个请求前,一个请求后,在请求前对请求的地址与参数做一个key值进行存储,后续进来的key值如果重复则取消请求(axios.CancelToken可取消请求),并通过key值去获取数据;在请求后将请求数据存储进对应的key值

请求前处理

// 存储请求
let pendingRequest = new Map()
// 获取请求唯一key值
function generateReqKey(config: YXRequestConfig){
  const { url, params } = config;
  const srt = new URLSearchParams(params);
  return  `${url}?${srt.toString()}`;
};
// 添加key值
function addPendingRequest(config: YXRequestConfig){
  const requestKey = generateReqKey(config);
  let resolve: res<unknown>;
  let reject: rej;
  const request: Promise<unknown> = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
  });
  config.cancelToken =
    config.cancelToken ||
    new axios.CancelToken((cancel) => {
      if (!pendingRequest.has(requestKey)) {
        pendingRequest.set(requestKey, {
          canceler: cancel,
          config: config,
          data: {
            response: request,
            resolve,
            reject
          }
        });
      }
    });
};
// 检查是否存在重复请求,若存在则取消已发的请求。
function removePendingRequest(config: YXRequestConfig){
  const requestKey = generateReqKey(config);
  if (pendingRequest.has(requestKey)) {
    const cancelToken = pendingRequest.get(requestKey);
    const res = cancelToken.data.response;
    cancelToken.canceler(res); // 取消请求
  }
};
// axios拦截器
axios.interceptors.request.use(
 (config: YXRequestConfig) => {
    removePendingRequest(config);
    addPendingRequest(config);
    return config;
  },
  (err) => {
    return Promise.reject(err);
  }
);

请求后处理

function changeRequest<T>(config: YXRequestConfig, response: T, flag: boolean){
  const requestKey = this.generateReqKey(config);
  if (this.pendingRequest.has(requestKey)) {
    const cancelToken = this.pendingRequest.get(requestKey);
    if (cancelToken) {
      if (flag) {
        cancelToken.data.resolve(response);
      } else {
        cancelToken.data.reject(response);
        this.pendingRequest.delete(requestKey);
      }
    }
  } else {
    throw new Error('未知请求');
  }
}
// axios请求后拦截
service.interceptors.response.use(
  function (response) {
    const {
      msg,
      code,
      msgType
    }: { msg: string; code: number; msgType: string } =
      response.data || response;
      if (code === 200) {
        changeRequest(response.config, response.data || response, true);
      } else changeRequest(response.config, response.data || response, false);
    return response;
  },
  function (error) {
    return Promise.reject(error);
  }
);

完整代码

import type { AxiosRequestConfig, Canceler } from 'axios';
import axios from 'axios';

type YXRequestConfig = AxiosRequestConfig & {
  headers?: any;
  noToken?: boolean; // 是否不需要token,默认false
  isLoading?: boolean; // 是否请求时开启全局Loading,默认false
  loadingTip?: string; // 全局Loading的提示
};
type res<T> = (value: T) => void;
type rej = (value: string) => void;
type TData<T> = {
  canceler: Canceler;
  config: YXRequestConfig;
  data: {
    data: Promise<T>;
    resolve: res<T>;
    reject: rej;
  };
};

export default class DuplicateRequest {
  constructor() {
    //
  }
  pendingRequest = new Map<string, TData<unknown>>();
  /**
   *
   * @param config 获取请求唯一key值
   * @returns
   */
  generateReqKey = (config: YXRequestConfig) => {
    const { url, params } = config;
    const srt = new URLSearchParams(params);
    const queryString = `${url}?${srt.toString()}`;
    return queryString;
  };

  /**
   *
   * @param config 填加pendingRequest
   * @returns
   */
  addPendingRequest = (config: YXRequestConfig) => {
    const requestKey = this.generateReqKey(config);
    let resolve: res<unknown>;
    let reject: rej;
    const request: Promise<unknown> = new Promise((res, rej) => {
      resolve = res;
      reject = rej;
    });
    config.cancelToken =
      config.cancelToken ||
      new axios.CancelToken((cancel) => {
        if (!this.pendingRequest.has(requestKey)) {
          this.pendingRequest.set(requestKey, {
            canceler: cancel,
            config: config,
            data: {
              data: request,
              resolve,
              reject
            }
          });
        }
      });
  };

  /**
   *
   * @param config 检查是否存在重复请求,若存在则取消已发的请求。
   */
  removePendingRequest = (config: YXRequestConfig) => {
    const requestKey = this.generateReqKey(config);
    if (this.pendingRequest.has(requestKey)) {
      const cancelToken = this.pendingRequest.get(requestKey);
      const res = cancelToken?.data.data;
      cancelToken?.canceler(res as any);
    }
  };

  /**
   *
   * @param config 请求成功返回数据
   */
  changeRequest = <T>(config: YXRequestConfig, response: T, flag: boolean) => {
    const requestKey = this.generateReqKey(config);
    if (this.pendingRequest.has(requestKey)) {
      const cancelToken = this.pendingRequest.get(requestKey);
      if (cancelToken) {
        if (flag) {
          cancelToken.data.resolve(response);
        } else {
          cancelToken.data.reject(response as any);
          this.pendingRequest.delete(requestKey);
        }
      }
    } else {
      throw new Error('未知请求');
    }
  };
}