axios防止重复请求

发布于:2025-03-19 ⋅ 阅读:(17) ⋅ 点赞:(0)

前言

大家在编写管理后端这种项目如何处理表单的重复请求的呢,我估计绝大部分是用loading处理的吧,用户点击的之后让按钮loading,直到请求完成之后再恢复;当然这样是可以的,只不过如果当页面比较多,这种方式实现起来相对来说比较麻烦;那么今天就说一个相对来说比较简单的方法

axios

项目是基于axios实现的功能;
主要实现原理,再请求拦截器中判断请求的url和请求方式还是请求参数是否一致,如果一致,代表是同一个请求,那么后面的请求不执行,直到第一个请求完成
代码:

import axios from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus';
import 'element-plus/theme-chalk/el-message.css';
import router from '../router/index';
// const noToken = ['/login', '/register', '/like/count'];
const token = ['/like', '/comment'];
const request = axios.create({
  baseURL: import.meta.env.VITE_BASE_URL,
  timeout: 5000,
  headers: {
    Authorization:
      JSON.parse(localStorage.getItem('userInfo') || '{}')['token-web'] || '',
    noToken: true,
  },
});
const newSet = new Set();
// 将请求地址和请求参数和方法生成一个字符串
const genKey = (config: any) => {
  const { method, url, data } = config;

  return data
    ? [
        url,
        method,
        typeof data !== 'string'
          ? JSON.stringify(data)
          : JSON.stringify(JSON.parse(data)),
      ].join('&')
    : [url, method].join('&');
};
// 添加请求拦截器
request.interceptors.request.use(
  (config: any) => {
    // console.log(router)
    // router.push('/login')
    // 如果请求地址包含like,删除请求头中的noToken
    if (token.some((path) => config.url.indexOf(path) !== -1)) {
      delete config.headers.noToken; // 如果 URL 包含数组中的任意一个元素,则删除 noToken
    }
    if (config.url.indexOf('/like/count') !== -1) {
      config.headers.noToken = true;
    }
    // 在发送请求之前做些什么 token

    // 获取请求地址和请求参数
    const key = genKey(config);

    // 每次请求前读取newSet中有没有key
    if (newSet.has(key)) {
      // 如果有,说明重复请求,返回一个错误
      return Promise.reject('重复请求');
    }
    // 如果没有,将key添加到newSet中
    newSet.add(key);
    const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
    config.headers.Authorization = 'Bear ' + userInfo['token-web'] || '';
    return config;
  },
  (error) => {
    // 对请求错误做些什么
    return Promise.reject(error);
  },
);
// 添加响应拦截器
request.interceptors.response.use(
  (response) => {
    // 对响应数据做点什么

    const key = genKey(response.config);
    // 每次请求后删除newSet中的key
    newSet.delete(key);
    console.log('添加响应拦截器');
    const res = response.data;
    // console.log(response)
    if (res.code && res.code !== 0) {
      // `token` 过期或者账号已在别处登录
      if (res.code === 401 || res.code === 4001) {
        router.push('/login');

        ElMessageBox.alert('你已被登出,请重新登录', '提示', {})
          .then(() => {})
          .catch(() => {});
      }

      return res;
    } else {
      return res;
    }
  },
  (error) => {
    if (error === '重复请求') {
      return Promise.reject('重复请求');
    }
    console.log(error);
    const key = genKey(error.config);
    // 每次请求后删除newSet中的key
    newSet.delete(key);
    console.log('添加响应拦截器');
    if (error.message === 'canceled') {
      return Promise.reject('取消请求');
    }
    if (error.response.data && error.response.data.message) {
      ElMessage.error(error.response.data.message);
      return error.response.data;
    } else {
      if (error.response.status === 401) {
        localStorage.clear();

        return;
      }

      // 对响应错误做点什么
      if (error.message.indexOf('timeout') != -1) {
        ElMessage.error('网络超时');
      } else if (error.message == 'Network Error') {
        ElMessage.error('网络连接错误');
      } else {
        if (error.response.data) ElMessage.error(error.response.statusText);
        else ElMessage.error('接口路径找不到');
        // ElMessage.error(error.message)
      }
      return Promise.reject(error);
    }
  },
);

// 导出 axios 实例
export default request;

  1. 当发送请求的时候先判断newSet中是否已经包含了当前发送的请求,如果已经包含,则表示发送的是重复请求,如果没有,正常发送,且将这个请求放入newSet中
  2. 当请求发送完成之后,不论成功还是失败,都将这个请求从newSet中移出,防止下次请求被拦截
    查看实际效果
    vue页面
<template>
  <div>
    <el-button @click="requestAxios">发送请求</el-button>
  </div>
</template>
<script setup lang="ts">
import request from '@/request/index';
import { test2 } from '@/request/test/index';

const requestAxios = () => {
  request.post('/api/table', {
    page: 1,
    limit: 10,
    size: {
      id: 1,
    },
  });
  request.post('/api/table', {
    page: 1,
    limit: 10,
    size: {
      id: 1,
    },
  });
  request.get('/api/table?page=1&limit=10');
  request.get('/api/table/1');
};

</script>
<style lang="scss" scoped></style>

正常不做处理的话,点击发送请求,会发送四个请求,如果做处理之后结果

image
image
只发送了三个请求,说明成功,再次点击发送,发现还是这三个请求,说明请求完成之后数据从newSet中移出了;
当短时间重复发送请求
image
全部被拦截了,达到了我们一开始说的那种功能

总结

以上就是实现的一种简单方法,当然源代码中的处理url、请求方式和参数的方式并不太好,大家可以自定义处理一下,如有问题请留言
npm i vue-template-cli-sys 用vue3+ts+element-plus+pinia编写的一个后台管理模板,里面的axios就是这种方式,如果大家不想重新搭建一个管理后台模板,即可安装,使用方式vsystemplate create 项目名称