思路: 拦截相同的请求,对于相同的请求,我们直接将它挂起,等到最先发出的请求拿到结果之后,把失败或成功的请求分享给后来的相同请求。
之前我们是通过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)
}