场景
在开发中会遇到连续点击按钮同时多次请求相同接口,一般我们会对按钮进行一个loading操作,但是有时候可能是一个tab页签,点击页签就要请求一次数据,当频繁的来回点击页签的时候,会发送多次请求,如果在数据还没有请求到的时候禁用tab页签点击,会给用户一种页签组件点不动的感觉,于是我考虑换个思路,发现本次的请求和之前的请求相同,则取消之前的请求,以本次的请求为准。
V1.0(使用CancelToken,无白名单,基本够用)
由于项目中使用到的是axios来进行请求,经过一通百度大法,发现axios有一个叫CancelToken的api,用于给请求打上一个标识,并返回一个函数,只要调用这个函数,就可以取消被标识的请求
整体思路如下:
1.定义请求队列,以唯一标识(url+method)为key来缓存取消函数
2.在请求拦截中判断,如果标识相同,说明为重复请求,根据唯一标识删除缓存队列中的取消函数,再将新的取消函数进行缓存
3.响应拦截中判断,如果本次请求成功,根据唯一标识删除缓存队列中的取消函数,如果请求失败,通过axios内置函数isCancel判断,本次失败的原因是接口请求失败,还是通过CancelToken进行的取消
import axios from 'axios'
// CancelToken能为一次请求‘打标识’
// isCancel用于判断请求是不是被CancelToken取消的
const { CancelToken, isCancel } = axios
// 请求队列,缓存发出的请求
const cacheRequest = {}
// axios实例
const service = axios.create({
baseURL: '/api',
headers: {
'Content-Type': 'application/json; charset=UTF-8'
},
})
// 请求拦截
service.interceptors.request.use((config) => {
const { url, method } = config
// 请求地址和请求方式组成唯一标识,将这个标识作为取消函数的key,保存到请求队列中
const reqKey = `${url}&${method}`
// 如果存在重复请求,删除之前的请求
removeCacheRequest(reqKey)
// 将请求加入请求队列
config.cancelToken = new CancelToken(c => {
cacheRequest[reqKey] = c
})
})
// 响应拦截
service.interceptors.response.use(
(response) => {
// 请求成功,从队列中移除
const { url, method } = response.config
removeCacheRequest(`${url}&${method}`)
},
(error) => {
// 请求失败,使用isCancel来区分是被CancelToken取消,还是常规的请求失败
if (isCancel(error)) {
// 通过CancelToken取消的请求不做任何处理
return Promise.reject({
message: '重复请求,自动拦截并取消'
})
} else{
// 正常请求发生错误,抛出异常等统一提示
console.log(error.response, 'errMsg')
}
}
)
/**
* @desc 删除缓存队列中的请求
* @param {String} reqKey 本次请求的唯一标识 url&method
*/
function removeCacheRequest(reqKey) {
if (cacheRequest[reqKey]) {
// 这里调用的就是上面的CancelToken生成的c函数,调用就会取消请求
cacheRequest[reqKey]()
delete cacheRequest[reqKey]
}
}
看起来没有问题了,确实也达到了我所想要的效果,之前的请求如果没有完成都被取消,始终只有最新的请求会被发出去
V2.0(使用AbortController,增加白名单)
回想自己用了这么久axios,都还不知道官方提供了这种api,于是我决定翻一下文档,复习一下axios,结果一翻不得了,官方标识从v0.22.0开始已经弃用CancelToken,建议我们使用AbortController来实现,由于项目中使用的是0.26.1(明明还是可以用),还是紧跟时代步伐,换成官方推荐的吧。
将上面的代码进行改造,顺便加了一个白名单,因为项目是vue的后台项目,有可能几个兄弟组件在初始化的时候都请求了同一个接口,这种情况下应该允许这些接口的重复请求
import axios from 'axios'
// 注意:AbortController是fetch API提供的,不需要从axios引入
// isCancel用于判断请求是不是被AbortController取消的
const { isCancel } = axios
// 请求队列,缓存发出的请求
const cacheRequest = {}
// 不进行重复请求拦截的白名单
const cacheWhiteList = ['/foo/bar&get']
// axios实例
const service = axios.create({
baseURL: '/api',
headers: {
'Content-Type': 'application/json; charset=UTF-8'
},
})
// 请求拦截
service.interceptors.request.use((config) => {
const { url, method } = config
// 请求地址和请求方式组成唯一标识,将这个标识作为取消函数的key,保存到请求队列中
const reqKey = `${url}&${method}`
// 如果存在重复请求,删除之前的请求
if (cacheWhiteList.indexOf(reqKey) === -1) {
removeCacheRequest(reqKey)
// 将请求加入请求队列,通过AbortController来进行手动取消
const controller = new AbortController()
config.signal = controller.signal
cacheRequest[reqKey] = controller
}
})
// 响应拦截
service.interceptors.response.use(
(response) => {
// 请求成功,从队列中移除
const { url, method } = response.config
removeCacheRequest(`${url}&${method}`)
},
(error) => {
// 请求失败,使用isCancel来区分是被CancelToken取消,还是常规的请求失败
if (isCancel(error)) {
// 通过CancelToken取消的请求不做任何处理
return Promise.reject({
message: '重复请求,自动拦截并取消'
})
} else{
// 正常请求发生错误,抛出异常等统一提示
console.log(error.response, 'errMsg')
}
}
)
/**
* @desc 删除缓存队列中的请求
* @param {String} reqKey 本次请求的唯一标识 url&method
*/
function removeCacheRequest(reqKey) {
if (cacheRequest[reqKey]) {
// 通过AbortController实例上的abort来进行请求的取消
cacheRequest[reqKey].abort()
delete cacheRequest[reqKey]
}
}
在查阅资料的过程中大部分教程都是使用的CancelToken按照项目的目前情况也够用,但是发现没有写的特别易懂的使用AbortController方式来取消的教程,所以这里也记录一下