axios处理重复请求的方法小结

 更新时间:2024年03月28日 11:25:46   作者:求知若饥  
这篇文章主要为大家详细介绍了如何使用发布订阅者模式来处理重复的axios请求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

使用到的技术/库:axiosTS发布订阅者模式

本文将使用发布订阅者模式来处理重复的axios请求。将实现“一定时间内发送多个相同的请求时,只向服务器发出第一个请求,然后将第一个请求的响应结果分发给各个请求方法”的效果。

前言

首先,我们要先定义何为重复的请求?

  • 接口地址、类型、参数相同,则视为相同的请求;
  • 上一个请求还未返回响应结果时,又发送一个相同的请求,则视为重复请求。

其次,我们将每次请求生成一个唯一的key并缓存起来,用作判断是否已存在相同的请求。

最后,我们需要实现一个发布订阅者模式,在存在重复请求时,中断请求,并添加订阅,当之前的请求结果返回时,发布给订阅者。

还有一个问题,如何中断请求?只需在axios的请求拦截器中返回一个Promise.reject()即可,这样将会直接执行axios的响应拦截器中的错误处理方法,而不会向服务器发送请求。

技术实现

1. 生成key

根据接口的地址、类型、参数,我们可以判断是否为相同的请求,那我们可以在key中包含这些关键信息。

import { type AxiosRequestConfig } from 'axios'

const getDataType = (obj: unknown) => {
  let res = Object.prototype.toString.call(obj).split(' ')[1]
  res = res.substring(0, res.length - 1).toLowerCase()
  return res
}

const getKey = (config: AxiosRequestConfig) => {
  const { method, url, data, params } = config;
  let key = `${method}-${url}`;

  try {
    if (data && getDataType(data) === 'object') {
      key += `-${JSON.stringify(data)}`;
    } else if (getDataType(data) === 'formdata') {
      for (const [k, v] of data.entries()) {
        if (v instanceof Blob) {
          continue;
        }

        key += `-${k}-${v}`;
      }
    }
    
    if (params && getDataType(params) === 'object') {
      key += `-${JSON.stringify(params)}`;
    }
  } catch (e) {console.error(e);}
  
  return key;
};

判断参数类型是为了处理FormData、二进制等格式情况。

2. 缓存请求

我们可以创建一个全局变量来保存请求信息,在请求拦截器(发送请求之前)中将请求信息添加至全局变量,然后在响应拦截器(请求完成之后)中移除相关信息。

import axios from 'axios';

const historyRequests = new Map<string, number>()

axios.interceptors.request.use(
  (config) => {
    // 生成key
    const key = createKey(config);
    // 响应拦截器中需要用到
    config.headers.key = key;
    // 缓存请求信息
    historyRequests.set(key, 1);

    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

instance.interceptors.response.use(
  (res) => {
    // 请求完成,删除缓存信息
    const key = res.config.headers.key as string;
    if (historyRequests.has(key)) {
      historyRequests.delete(key);
    }

    return res;
  },
  (error) => {
    return Promise.reject(error);
  }
);

3. 发布订阅者模式实现

订阅者通过注册事件到调度中心,当发布者触发事件时,由调度中心执行相应的订阅事件。

export default class EventBus<T extends Record<string | symbol, any>> {
  private listeners: Record<keyof T, ((...args: any[]) => void)[]> = {} as any

  $on<K extends keyof T>(event: K, callback: T[K]) {
    if (!this.listeners[event]) {
      this.listeners[event] = []
    }
    this.listeners[event].push(callback)
  }

  $emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>) {
    const callbacks = this.listeners[event]
    if (!callbacks || callbacks?.length === 0) {
      return
    }

    callbacks.forEach((callback) => {
      callback(...args)
    })
  }

  $off<K extends keyof T>(event: K, listener?: T[K]) {
    if (!listener) {
      delete this.listeners[event]
      return
    }

    const fns = this.listeners[event]
    if (!fns || !fns.length) {
      return
    }

    const idx = fns.indexOf(listener)
    if (idx !== -1) {
      fns.splice(idx, 1)
    }
  }

  clear() {
    this.listeners = {} as any
  }
}

4. 完整代码实现

import axios, {
  type AxiosRequestConfig,
  AxiosError,
  type AxiosResponse
} from 'axios';
import { getDataType } from './utils';
import { EventBus } from './event/eventBus';

interface IBaseResponse {
  code: number;
  msg: string;
  data?: unknown;
}

const createKey = (config: AxiosRequestConfig) => {
  const { method, url, data, params } = config;
  let key = `${method}-${url}`;

  try {
    if (data && getDataType(data) === 'object') {
      key += `-${JSON.stringify(data)}`;
    } else if (getDataType(data) === 'formdata') {
      for (const [k, v] of data.entries()) {
        if (v instanceof Blob) {
          continue
        }
        key += `-${k}-${v}`;
      }
    }
    if (params && getDataType(params) === 'object') {
      key += `-${JSON.stringify(params)}`;
    }
  } catch (e) {console.error(e);}
  return key;
};

const instance = axios.create({
  baseURL: '',
  timeout: 5000,
  withCredentials: true,
  headers: {
    'Content-Type': 'application/json;'
  }
});

const historyRequests = new Map<string, number>();
instance.interceptors.request.use(
  (config) => {
    const key = createKey(config);
    // 在响应拦截器中需要用到该key发布/订阅事件
    config.headers.key = key;
    // 判断是否存在相同请求,存在则中断请求
    if (historyRequests.has(key)) {
      // 为了后续方便处理中断请求超时
      config.headers.requestTime = Date.now();
      
      // 抛出错误并传递相应参数
      return Promise.reject(
        new AxiosError('Redundant request', 'ERR_REPEATED', config)
      );
    }
    historyRequests.set(key, 1);
    return config;
  },
  (error: AxiosError) => {
    return Promise.reject(error);
  }
);

const responseInterceptor = (res: AxiosResponse<IBaseResponse | Blob>) => {
  const result: [
    AxiosResponse<IBaseResponse | Blob> | undefined,
    AxiosError | undefined
  ] = [undefined, undefined];
  const data = res.data;
  // 可根据你的接口响应数据作处理,这里假设code不为200都为错误
  if (data instanceof Blob || data.code === 200) {
    result[0] = res;
  } else {
    result[1] = new AxiosError(data.msg);
  }
  return result;
};

const eventBus = new EventBus<{
  [key: string]: (
    data?: AxiosResponse<IBaseResponse | Blob>,
    error?: AxiosError
  ) => void;
}>();

instance.interceptors.response.use(
  (res) => {
    const [data, error] = responseInterceptor(res);

    // 如果存在重复请求,则发布结果,执行订阅事件
    const key = res.config.headers.key as string;
    if (historyRequests.has(key)) {
      historyRequests.delete(key);
      eventBus.$emit(key, data, error);
    }

    return data !== undefined ? data : Promise.reject(error);
  },
  (error: AxiosError) => {
    // 处理中断的重复请求
    if (error.code === 'ERR_REPEATED') {
      return new Promise((resolve, reject) => {
        const config = error.config!;
        const key = config.headers.key as string;
        const callback = (
          res?: AxiosResponse<IBaseResponse | Blob>,
          err?: AxiosError
        ) => {
          res ? resolve(res) : reject(err);
          eventBus.$off(key, callback);
        };
        // 订阅事件
        eventBus.$on(key, callback);

        // 处理超时
        const timeout = config.timeout || 5000;
        const requestTime = config.headers.requestTime as number;
        const now = Date.now();
        if (now - requestTime > timeout) {
          historyRequests.delete(key);
          const error = new AxiosError(
            `timeout of ${timeout}ms exceeded`,
            'ECONNABORTED',
            config
          );
          error.name = 'AxiosError';
          eventBus.$emit(key, undefined, error);
        }
      });
    }

    return Promise.reject(error);
  }
);

可以根据你的实际需求调整相关代码,例如:你需要对某些请求放行,则可以通过在headers中添加一个属性来控制是否允许重复请求。

以上就是axios处理重复请求的方法小结的详细内容,更多关于axios处理重复请求的资料请关注脚本之家其它相关文章!

相关文章

  • JavaScript实现判断图片是否加载完成的3种方法整理

    JavaScript实现判断图片是否加载完成的3种方法整理

    这篇文章主要介绍了JavaScript实现判断图片是否加载完成的3种方法整理,本文讲解了onload方法、javascipt原生方法、jquery方法三种方法,需要的朋友可以参考下
    2015-03-03
  • 微信小程序实现的贪吃蛇游戏【附源码下载】

    微信小程序实现的贪吃蛇游戏【附源码下载】

    这篇文章主要介绍了微信小程序实现的贪吃蛇游戏,结合实例形式分析了微信小程序实现贪吃蛇游戏功能的相关界面布局与代码逻辑操作技巧,并附带源码供读者下载参考,需要的朋友可以参考下
    2018-01-01
  • JS仿百度自动下拉框模糊匹配提示

    JS仿百度自动下拉框模糊匹配提示

    这篇文章主要介绍了JS仿百度自动下拉框模糊匹配提示 的相关资料,需要的朋友可以参考下
    2016-07-07
  • JavaScript脚本性能优化注意事项

    JavaScript脚本性能优化注意事项

    本文总结了我在JavaScript编程中所找到的提高JavaScript运行性能的一些方法,其实这些经验都基于几条原则
    2008-11-11
  • JavaScript实现99乘法表及隔行变色实例代码

    JavaScript实现99乘法表及隔行变色实例代码

    最近做了个项目是要求实现99乘法表隔行变色,本文给大家分享通过多种方式实现js 99 乘法表,感兴趣的朋友一起看看吧
    2016-02-02
  • clipboard.js在移动端复制失败的解决方法

    clipboard.js在移动端复制失败的解决方法

    最近在使用clipboard.js碰到的一个小问题,通过查找相关资料解决了,所以下面这篇文章主要给大家介绍了关于clipboard.js在移动端复制失败的解决方法,需要的朋友可以参考借鉴,下面来一起学习学习吧
    2018-06-06
  • 23个Javascript弹出窗口特效整理

    23个Javascript弹出窗口特效整理

    23个Javascript弹出窗口特效,需要的朋友可以参考下。
    2011-02-02
  • 解决JS使用fill()进行数组填充遇到的问题

    解决JS使用fill()进行数组填充遇到的问题

    最近在做算法题时,遇到需要创建二维数组并进行初始化的情况,刚开始我使用的是 new Array(n).fill(new Array(n).fill('.')) 进行二维数组的初始化,但无论怎样我都通不过测试用例,所以本文就给大家详细的介绍了如何解决这类问题以及将js中的fill(方法重学一下
    2023-09-09
  • JavaScript常用的工具函数分享

    JavaScript常用的工具函数分享

    这篇文章主要介绍了JavaScript常用的工具函数分享,JavaScript 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言,下文详细介绍需要的小伙伴可以参考一下
    2022-03-03
  • JS绘制微信小程序画布时钟

    JS绘制微信小程序画布时钟

    微信小程序官方组件也提供了画布功能,下面分享一下如何创建微信小程序画布时钟
    2016-12-12

最新评论