方案一:请求队列
拦截相同请求:对于相同的请求,先挂起,等最先发出去的请求拿到数据后,把成功或失败的结果共享给后面的相同请求。
- 拿到结果后,返回给之前挂起的请求,使用发布订阅模式
- 挂起的请求,需要拦截,所以在请求拦截器中通过return Promise.reject()直接中断请求,做特殊标记,便于在响应拦截器中处理。
import axios from 'axios'
let instance = axios.create({
baseUrl: '/api/',
})
//发布订阅
class EventEmitter {
constructor() {
this.event = {}
}
on(type, cbres, cbrej) {
if (!this.event[type]) {
this.event[type] = [[cbres, cbrej]]
} else {
this.event[type].push([cbres, cbrej])
}
}
emit(type, res, ansType) {
if (!this.event[type]) return
else {
this.event[type].forEach((cbArr) => {
if (ansType === 'resolve') {
cbArr[0](res)
} else {
cbArr[1](res)
}
})
}
}
}
//根据请求生成对应的key
function generateReqKey(config, hash) {
const { method, url, params, data } = config
return [method, url, JSON.stringify(params), JSON.stringify(data), hash].join(
'&'
)
}
// 对于请求体data中的数据是FormData类型的处理
function isFileUploadApi(config) {
return Object.prototype.toString.call(config.data) === '[object FormData]'
}
// 存储已发送但未响应的请求
const pendingRequest = new Set()
// 发布订阅容器
const ev = new EventEmitter()
//添加请求拦截器
instance.interceptors.request.use(
async (config) => {
let hash = location.hash
//生成请求key
let reqKey = generateReqKey(config, hash)
if (!isFileUploadApi(config) && pendingRequest.has(reqKey)) {
// 如果是相同请求,在这里将请求挂起,通过发布订阅来为该请求返回结果
// 这里需注意,拿到结果后,无论成功与否,都需要return Promise.reject()来中断这次请求,否则请求会正常发送至服务器
let res = null
try {
// 接口成功响应
res = await new Promise((resolve, reject) => {
ev.on(reqKey, resolve, reject)
})
return Promise.reject({
type: 'limiteResSuccess',
val: res,
})
} catch (limitFunErr) {
// 接口报错
return Promise.reject({
type: 'limiteResError',
val: limitFunErr,
})
}
} else {
// 将请求的key保存在config
config.pendKey = reqKey
pendingRequest.add(reqKey)
}
return config
},
function (error) {
return Promise.reject(error)
}
)
// 添加响应拦截器
instance.interceptors.response.use(
function (response) {
// 将拿到的结果发布给其他相同的接口
handleSuccessResponse_limit(response)
return response
},
function (error) {
return handleErrorResponse_limit(error)
}
)
// 接口响应成功
function handleSuccessResponse_limit(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.event[reqKey]
}
}
// 接口走失败响应
function handleErrorResponse_limit(error) {
if (error.type && error.type === 'limiteResSuccess') {
return Promise.resolve(error.val)
} else if (error.type && error.type === 'limiteResError') {
return Promise.reject(error.val)
} else {
const reqKey = error.config.pendKey
if (pendingRequest.has(reqKey)) {
let x = null
try {
x = JSON.parse(JSON.stringify(error))
} catch (e) {
x = error
}
pendingRequest.delete(reqKey)
ev.emit(reqKey, x, 'reject')
delete ev.event[reqKey]
}
}
return Promise.reject(error)
}
export default instance
方案二:利用axios提供的cancelToken
通过axios请求拦截器,在每次请求前把请求信息和请求的取消方法放到一个map对象当中,并且判断map对象当中是否已经存在该请求信息的请求,如果存在取消上次请求
方案三:使用状态管理库
vue中,可以结合状态管理库管理请求状态,确保只有一个请求在进行,避免重复请求。