公司项目处理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)
        //异常处理
      })
    }
  })
}