公司项目处理ajax重复请求的几个阶段
第一阶段,遮罩层
只对需要提交数据的请求做防重复,对查询请求不做防重复功能。在点击按钮做提交数据请求时,生成一个全屏遮罩层,防止下一次点击。
function submitRequest(){
showLoading('提交中')
// 发请求
axios().then(res=>{
Toast.clear()
//逻辑处理
},err=>{
Toast.clear()
//异常处理
})
}
function showLoading (text) {
Toast.loading({
message: text,
duration: 30 * 1000,
overlay: true
})
}
第二阶段,后端拦截
重复请求功能,使用jwt+url+参数
作为key,存入redis,有效期2秒,查询redis中有无这个key来判断是否为重复请求,若为重复请求,抛出异常给前端。这里用java来做示例
private Boolean checkDuplicate(String key){
Boolean duplicate = (Boolean) redisTemplate.execute((RedisCallback<Boolean>) c -> {
byte[] ret = c.get(key.getBytes());
if (ret == null) {
/** 如果2个请求之间特别快,毫秒级之间。例如扫码绑定,SB微信会连续发2次一样的请求,需要使用setNX方法获取锁 setNX 为 SET if Not eXists, false表示该key已存在, true设置成功 */
Boolean exist = c.setNX(key.getBytes(), "1".getBytes());
c.expire(key.getBytes(), 2); //设置超时时间2秒
if(exist){
return false;
}
}
return true;
});
if (duplicate) {
log.info("2秒内重复提交,key:{}", key);
}
return duplicate;
}
第三阶段,前端拦截重复请求
使用一个Set
来储存正在执行中的请求,key为url+参数
,请求结束,无论成功失败,删除该key。每次请求时查询Set
中有无同样的请求,有则toast提示重复请求
function request (params, url) {
const paramsStr = qs.stringify(params)
// 校验重复请求
const requestItem = url + '?' + qs.stringify(params)
if (requestingUrl.has(requestItem)) {
console.error('请勿重复请求URL:' + url)
showToast('请勿重复请求')
return
}
requestingUrl.add(requestItem)
// 发请求
axios().then(res=>{
requestingUrl.delete(requestItem)
//逻辑处理
},err=>{
requestingUrl.delete(requestItem)
//异常处理
})
}
第四阶段,重复请求阻塞,等待相同请求的返回值
当多个同样的请求发出时,只有第一个会真正到达后端,其余几个阻塞,当第一个请求返回时,将返回值赋给其他正在阻塞的请求。
方案可以查看这一篇的方案二,具体方法就是若判断有相同的请求正在执行中,将返回request请求返回的promise中的resolve和reject函数保存下来,待相同的请求返回,执行保存的resolve或reject函数,将返回值当做参数传入。
const requestingMap = new Map()
function request (url, params) {
const ajaxPromise = new Promise((resolve, reject) => {
const paramsStr = qs.stringify(params)
const requestingKey = url + '?' + paramsStr
// 有同样的请求正在执行,等待执行完一起返回
if (requestingMap.has(requestingKey)) {
requestingMap.get(requestingKey).push({
resolve,
reject
})
}
// 正常请求
else {
requestingMap.set(requestingKey, [{
resolve,
reject
}])
axios().then(res => {
requestingMap.get(requestingKey).forEach(item => item.resolve(res))
requestingMap.delete(requestingKey)
//逻辑处理
}, err => {
requestingMap.get(requestingKey).forEach(item => item.reject(err))
requestingMap.delete(requestingKey)
//异常处理
})
}
})
}