一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情。
在项目中,我们可能需要对请求进行‘防抖’处理。主要为了阻止用户在某些情况下,短时间内重复点击某个按钮,导致前端向后端重复发送多次请求。常见的情况:
PC 端 - 用户双击搜索按钮/提交表单,可能会触发两次搜索请求/重复提交数据
移动端 - 因移动端没有点击延迟,因此极容易造成误操作或者多操作,造成重复请求
可能有 loading 遮罩层依然发生,因此我们要考虑前端阻止重复请求的方法。
首先我们需要了解一下 axios
库中的 cancelToken API,其主要是用于取消接口请求的核心 API。在官网文档中主要有两个方法使用,代码如下:
方法1:通过axios.CancelToken.source
生成取消令牌token
和取消方法cancel
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// handle error
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
方法2:通过axios.CancelToken
构造函数生成取消函数
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
cancel = c;
})
});
// cancel the request
cancel();
需要注意的是在
catch
中捕获异常时,应该使用axios.isCancel()
判断当前请求是否是主动取消的,以此来区分普通的异常逻辑。
最终封装
封装1
// 正在进行中的请求列表
let reqList = []
/**
* 阻止重复请求
* @param {array} reqList - 请求缓存列表
* @param {string} url - 当前请求地址
* @param {function} cancel - 请求中断函数
* @param {string} errorMessage - 请求中断时需要显示的错误信息
*/
const stopRepeatRequest = function (reqList, url, cancel, errorMessage) {
const errorMsg = errorMessage || ''
for (let i = 0; i < reqList.length; i++) {
if (reqList[i] === url) {
cancel(errorMsg)
return
}
}
reqList.push(url)
}
/**
* 允许某个请求可以继续进行
* @param {array} reqList 全部请求列表
* @param {string} url 请求地址
*/
const allowRequest = function (reqList, url) {
for (let i = 0; i < reqList.length; i++) {
if (reqList[i] === url) {
reqList.splice(i, 1)
break
}
}
}
const service = axios.create()
// 请求拦截器
service.interceptors.request.use(
config => {
let cancel
// 设置cancelToken对象
config.cancelToken = new axios.CancelToken(function(c) {
cancel = c
})
// 阻止重复请求。当上个请求未完成时,相同的请求不会进行
stopRepeatRequest(reqList, config.url, cancel, `${config.url} 请求被中断`)
return config
},
err => Promise.reject(err)
)
// 响应拦截器
service.interceptors.response.use(
response => {
// 增加延迟,相同请求不得在短时间内重复发送
setTimeout(() => {
allowRequest(reqList, response.config.url)
}, 1000)
// ...请求成功后的后续操作
// successHandler(response)
},
error => {
if (axios.isCancel(thrown)) {
console.log(thrown.message);
} else {
// 增加延迟,相同请求不得在短时间内重复发送
setTimeout(() => {
allowRequest(reqList, error.config.url)
}, 1000)
}
// ...请求失败后的后续操作
// errorHandler(error)
}
)
封装2
axios.interceptors.request.use(config => {
removePending(options) // 在请求开始前,对之前的请求做检查取消操作
addPending(options) // 将当前请求添加到 pending 中
// other code before request
return config
}, error => {
return Promise.reject(error)
})
axios.interceptors.response.use(response => {
removePending(response) // 在请求结束后,移除本次请求
return response
}, error => {
if (axios.isCancel(error)) {
console.log('repeated request: ' + error.message)
} else {
// handle error code
}
return Promise.reject(error)
})
axios.interceptors.request.use(config => {
removePending(options) // 在请求开始前,对之前的请求做检查取消操作
addPending(options) // 将当前请求添加到 pending 中
// other code before request
return config
}, error => {
return Promise.reject(error)
})
axios.interceptors.response.use(response => {
removePending(response) // 在请求结束后,移除本次请求
return response
}, error => {
if (axios.isCancel(error)) {
console.log('repeated request: ' + error.message)
} else {
// handle error code
}
return Promise.reject(error)
})
router.beforeEach((to, from, next) => {
clearPending()
// ...
next()
})