前言✨
日常开发中常常遇到提交一个表单,如果前后端不做限制的话会多次触发,导致多次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,表示该接口开启截重复请求,默认为false。
3.在接口请求时,如果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()
})
}
最终效果如下:
参考
很多文案和代码都是参考如下,在完全了解后再加上自己项目的业务逻辑
Axios 如何取消重复请求? juejin.cn/post/695561…
axios请求拦截重复请求-阻止表单重复提交 juejin.cn/post/693873…