在项目中,我们可能需要对请求进行‘防抖’处理。主要为了阻止用户在某些情况下,短时间内重复点击某个按钮,导致前端向后端重复发送多次请求。常见的情况:
PC 端 - 用户双击搜索按钮/提交表单,可能会触发两次搜索请求/重复提交数据
移动端 - 因移动端没有点击延迟,因此极容易造成误操作或者多操作,造成重复请求
可能有 loading 遮罩层依然发生,因此我们要考虑前端阻止重复请求的方法。
首先我们需要了解一下 axios 库中的 cancelToken API,其主要是用于取消接口请求的核心 API。在官网文档中主要有两个方法使用,代码如下:
方法一:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token // 必须对请求进行cancelToken设置
}).catch(function (thrown) {
if (axios.isCancel(thrown)) { // 如果请求被取消则进入该方法判断
console.log('Request canceled', thrown.message);
} else {
// handle error
}
});
// 取消上面的请求
// source.cancel('messge') message为可选项,必须为String
source.cancel('Operation canceled by the user.');
方法二:
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
// 在options中直接创建一个cancelToken对象
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
});
// 取消上面的请求
cancel();
有了官方提供的 API,那我们如何封装用于项目中呢?axios 作为请求的配置,有请求拦截器和响应拦截器作为全局请求的配置管理。首先我们对所有正在进行中的请求进行缓存,在请求发起前判断请求是否正在进行,如果有,则取消该请求。在完成请求之后,我们需要将缓存列表中的该次请求删除,以便可以重新发送该请求。
// request拦截器
$http.interceptors.request.use(config => {
// 如果想请求可以重复发起,给在请求参数中加allowedRepeat:true (后续会删除,不会发送给服务端)
if (!config.data || !config.data.allowedRepeat) { // 如果不允许重复请求,开启拦截
// todo: 1. 设置拦截 防止重复请求
// 拦截重复请求(即当前正在进行的相同请求)
const requestData = getRequestIdentify(config)
removePending(requestData, true)
// 使用 cancel token 取消请求 参考:http://www.axios-js.com/zh-cn/docs/#%E6%8B%A6%E6%88%AA%E5%99%A8
config.cancelToken = new CancelToken((c) => {
pending[requestData] = c
})
} else { // 允许重复请求,不进行拦截
delete config.data.allowedRepeat // 把自定义的请求参数给删掉,不发送给服务端
}
return config
}, error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
})
getRequestIdentify 方法将请求 url 和请求参数作为是否是同一请求的判断;
removePending 方法是利用对象的 key 是否存在判断是否为需要取消重复请求;
这里的 key 就是有请求的 url 和 请求参数组合而成。代码实现如下:
// 拦截重复请求
let pending = {}
const CancelToken = axios.CancelToken
// 请求标识;完成请求后也需要执行删除记录,所以添加此参数避免执行无用操作
const removePending = (key, isRequest = false) => {
if (pending[key] && isRequest) {
pending[key]('取消重复请求')
}
delete pending[key]
}
/**
* 由于我们请求用了代理 直接代理到测试服务器 因此请求响应拦截器的config.url是一致的,不需要标识值区分
* 如果请求拦截器和响应拦截器的config.url不一致,就需要一个标识值用来区分判断
*/
const getRequestIdentify = (config) => {
const url = config.url
// 返回url及请求参数 post方法请求参数为config.data get方法请求参数为config.params
if (config.method === 'post') {
return encodeURIComponent(config.url + JSON.stringify(config.data))
}
return encodeURIComponent(url + JSON.stringify(config.params))
}