前端接口防止重复请求(重复的请求使用同一返回结果)

294 阅读2分钟

思路: 拦截相同的请求,对于相同的请求,我们直接将它挂起,等到最先发出的请求拿到结果之后,把失败或成功的请求分享给后来的相同请求。

之前我们是通过axios的cancelToken去取消请求,但是这个方法会直接在前端阻断请求的发起。使用发布订阅模式,对于挂起的请求,我们需要对其进行拦截,并且return Promise.reject()去中断请求,在响应拦截器中对其进行特殊的处理。

实现代码:

import axios from "axios"
let instance = axios.create({ baseURL: "/api/" })

class EventEmitter {
   constructor(){
     // 此对象的key就是每个请求产生的唯一key  值是promise中resolve和reject的数组
     this.event = {}
   }
   on(type, cbres, cerej) {
     if(!this.event[type]) {
        this.event[type] = [[cbres, cerej]]
     } else {
        this.event[type].push([cbres, cerej])
     }
   }
   emit(type, res, ansType) {
     if(!this.event[type]) return
     this.event[type].forEach(cbArr => {
       if(ansType === 'resolve') {
         cbArr[0](res)
       } else {
         cbArr[1](res)
       }
     })
   }
}

// 根据请求生成的唯一标识每个请求的key
const generateReqKey = (config, has) => {
  const { method, url, params, data } = config
  return [method, url, JSON.stringify(params), Json.stringify(data), hash].join('&')
}
// 已发送但未响应的请求
const pendingRequest = new Set()

// 发布订阅的容器
const ev = new EventEmitter()

// 当是人家上传时,生成的reqKey会是相同的,需要将此情况区别出来
const isFileUpload = config = > Object.prototype.toString.call(config.data) === "[object FormData]"


// 请求拦截器
instance.interceptors.request.use(async config => {
  if(isFileUpload(config)) return config
  let hash = location.has
  let reqKey = generateReqKey(config, hash)
  if(pendingRequest.has(reqKey)) {
    // 如果已经有相同的请求,这个请求将在这里挂起,等待上一个请求返回后再返回相同的内容
    let res = null
    try{
      res = await new Promise((res, rej) => {
        ev.on(reqKey, res, rej)
      })
      return Promise.reject({
        type: 'ReqSuccess',
        val: res
      })
    }catch(err) {
      return Promise.reject({
        type: 'ReqError,
        val: err
      })
    }
  } else {
    config.pendKey = reqKey
    pendingRequest.add(reqKey)
  }
  return config
}, function (error) {
  return Promise.reject(error)
})

// 请求响应拦截器
instance.interceptors.response.use(function(response) {
  // 将拿到的结果发布给其他相同的接口
  if(isFileUpload(response.config)) return response
  handleSuccessResponse(response)
  return response
}, function(error) {
  // 根据不同的返回结果处理
  if(isFileUpload(error.config)) return Promise.reject(error)
  return handleErrorResponse(error)
})

// 接口响应成功
const handleSuccessResponse = (response) => {
  const reqKey = response.config.pendKey
  if(pendingRequest.has(reqKey)) {
    let x = null
    try{
      x = JSON.parse(JSON.stringify(response))
    }catch (e){
      x = response
    }
    pendingRequest.delete(reqKey)
    ev.emit(reqKey, x, 'resolve')
    delete ev.reqKey
  }
}
// 接口响应失败
const handleErrorResponse = error => {
  if(error.type && error.type === 'ReqSuccess') {
    return Promise.resolve(error.val)
  } else if(error.type && error.type === 'ReqError') {
    return Promise.reject(error.val)
  } else {
    const reqKey = error.config.pendKey
    if(prndingRequest.has(reqKey)) {
      let x = null
      try{
       x = JSON.parse(JSON.stringify(error))
      }catch(e){
        x = error
      }
      pendingRequest.delete(reqKey)
      ev.emit(reqKey, x, 'reject')
      delet  ev.reqKey
    }
  }
  return Promise.reject(error)
}