axios拦截重复请求

1,631 阅读3分钟

前言✨

closePreventRequest.gif 日常开发中常常遇到提交一个表单,如果前后端不做限制的话会多次触发,导致多次UPDATE、INSERT,如上图所示。

解决方案: 后端可以采用接口幂等性、加锁来限制,前端可以采用防抖、axios接口取消请求、按钮disabled等方案各有利弊,接下来前端解决方案。

1.防抖

防抖是固定设置时间,但是接口响应的时间是不固定的,而且页面经常会调取多个接口会导致其他同时调用的接口没有触发,还得做校验区分。缺点:无法解决同一接口重复调取问题

2.axios取消请求

利用axios提供的cancelToken来取消接口。缺点:前端即使取消了,但是面对毫秒级的接口其实已经到服务端了。

3.按钮disabled/loading

主要代码逻辑就是接口调取的时候开启loading或disabled,在promise状态为fulfilled和rejected时关闭loading或disabled,是目前比较好的方案,配上loading动画体验感也相当不错。缺点:无法全局加,接口和按钮耦合度较高。

实现方案🛠

思路

在axios中拦截同一接口的重复触发,只要找到区分这个接口是不是同一个,如果是同一个我们就可以在接口发出请求之前拦截它,而不是请求后在取消。

1.定义一个数组存放
2.axios中定义一个是否开启拦截请求的开关openPreventRequest,值为true,表示该接口开启截重复请求,默认为false3.在接口请求时,如果openPreventRequest为true,先校验array里是否有相同的请求请求,如果有,则拦截本次请求不发出,如果没有,将该请求信息添加进array里。
4.在数据返回后(无论成功或者失败),再将array里该请求的信息移除,保证相同请求array里只有一条。
5.切换路由,重置array

实现

一、封装函数

// preventRequest.js
import qs from "qs";
import md5 from 'js-md5';

// 缓存请求的接口信息
const requestMap = []

/**
 * 检查是不是重复请求
 * @param {Object} config
 */
const checkRepeatRequest = config => {
  const requestInfo = getRequestInfo(config)
  return requestMap.includes(requestInfo)
}

/**
 * 添加请求
 * @param {Object} config
 */
const addRequest = config => {
  // 获取当前请求信息
  if (!config.openPreventRequest) return
  const requestInfo = getRequestInfo(config)
  requestMap.push(requestInfo)
}
/**
 * 移除请求
 * @param {Object} config
 */
const removeRequest = config => {
  if (!config.openPreventRequest) return
  const requestInfo = getRequestInfo(config)
  const requestIndex = requestMap.indexOf(requestInfo)
  if (requestIndex > -1) {
    requestMap.splice(requestIndex, 1)
  }
}

/**
 * 获取请求信息
 * @param {Object} config
 */
function getRequestInfo(config) {
  const { url, data } = config
  const obj = {
    ...data,
    url: url,
    method: method
  }
  const sign = md5(qs.stringify(obj)).toUpperCase(); // 这样才能区分是不是同接口且同参数
  return sign
}

/**
 * 清空请求数组
 */
function clearRequestMap() {
  requestMap.length = 0
}

export default {
  checkRepeatRequest,
  addRequest,
  removeRequest,
  clearRequestMap
}

二、axios配置(已精简)

import axios from 'axios'
import preventRequest from './preventRequest'

// http request 拦截器
http.interceptors.request.use(config => {
// 重复请求拦截开关 默认flase
  config.openPreventRequest = config.url.openPreventRequest || false
 if (config.openPreventRequest) {
    // 检查是否存在重复请求,若存在则取消已发的请求
    if (preventRequest.checkRepeatRequest(config)) {
      return Promise.reject(new Error('请求重复已拦截:' + config.url))
    }
    preventRequest.addRequest(config); // 把当前请求信息添加到pendingRequest对象中
  }
  return config
}, error => {
  return Promise.reject(error).catch(error);
})

// http response 拦截器
http.interceptors.response.use(
  (response) => {
  const data = response.data
  // 开启重复请求拦截的接口请求结束后要清除掉
      if (response.config.openPreventRequest) {
        if (typeof response.config.data === 'string') {
          // response里面返回的config.data是个字符串对象
          response.config.data = JSON.parse(response.config.data)
        }
        preventRequest.removeRequest(response.config)
      }
      return data
  },(error) => {
    if (error) {
      //网络超时异常处理
      if (error.code === 'ECONNABORTED' || error.message === "Network Error" || error.message.includes("timeout")) {
        Message.error('请求超时,请稍后重试')
        preventRequest.clearRequestMap()
      }
    }
    return Promise.reject(error).catch(error);
  }

三、接口封装处开启重复请求限制

export const addNewStamp = param => {
  return http.post({
    url: '/oa/xxx/xx',
    data: param,
    openPreventRequest: true // 开启限制
  })
};

四、路由跳转后清除

vue的路由守卫,如果是react就自行手写个路由守卫

// router/index.js
import preventRequest from "@/utils/preventRequest"
export default (router) => {
  router.beforeEach(async (to, from, next) => {
    preventRequest.clearRequestMap()
    // do something
    return next()
  })
}

最终效果如下:

openPreventRequest2.gif

参考

很多文案和代码都是参考如下,在完全了解后再加上自己项目的业务逻辑

Axios 如何取消重复请求? juejin.cn/post/695561…

axios请求拦截重复请求-阻止表单重复提交 juejin.cn/post/693873…