背景
对于后台系统,重复提交是一个特别常见的问题,目前我们的做法是,添加loading,或者是临时根据一个变量来判断是否可以提交,这种方法没有问题,缺点就是需要每次都声明变量,并且容易被开发者忘记,所以我们在想,是否可以添加一个全局的axios拦截,将重复的请求都去掉?
接口取消请求
axios文档中其实由一个专门用于取消请求的工厂方法CancelToken.source,它是基于proposal-cancelable-promises的,需要注意的是,它目前还处于第一阶段,使用方法如下:
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 {
// 处理错误
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
CancelToken.source方法会返回一个对象source,在需要取消的axios请求配置中加入cancelToken:source.token,调用source.cancel()方法即可取消掉该请求
全局配置
在全局维护一个队列,在每次请求的同时,判断一下该队列中是否已经存在相同的请求,如果有,则将当前请求取消掉,如果没有,则正常请求,并且加入到pending队列中,当请求结束后,将该请求从队列中删除即可
注意: 1. 对一个请求是否重复的判断,需要你根据项目的实际情况来判断,可能有的接口是相同名字但是参数不同,可能有的请求是前端需要进行循环请求的,它们就都是可以进行重复请求的,所以对于重复的定义,请自行斟酌! 2. cancelToken基于proposal-cancelable-promises,它还处在第一阶段 |
---|
// axios.js
let pendingQueue = new Map()
// 取消需要从全局的axios中拿,如果你用了create,也需要拿导入的axios(随便你命名)
let CancelToken = axios.CancelToken;
// 拦截
axios.interceptors.request.use((config) => {
// 可能需要根据传参来判断是否需要进行重复请求校验
// if (config.data && !config.data.GLOBAL_CANCEL) {
// let flag = judgePendingFuncNew(config);
// // 将pending队列中的请求设置为当前
// let source = CancelToken.source();
// config.cancelToken = source.token;
// if (flag) {
// // 当前的请求是重复的
// source.cancel('取消请求');
// } else {
// // 当前请求是一个新请求
// pendingQueue.set(`${config.method}->${config.url}`, true);
// }
// }
// 请求发起之前先检验该请求是否在队列中,如果在就把队列中pending的请求cancel掉
// 此处还可以添加全局的loading,但是需要记住在response中关掉loading,同时在请求失败的catch中也关闭loading
let flag = judgePendingFuncNew(config);
// 将pending队列中的请求设置为当前
let source = CancelToken.source();
config.cancelToken = source.token;
if (flag) {
// 当前的请求是重复的
source.cancel('取消请求');
} else {
// 当前请求是一个新请求
pendingQueue.set(`${config.method}->${config.url}`, true);
}
// ...
})
// 响应
axios.interceptors.response.use(response => {
removeResolvedFunc(response.config)
// ...
)}
const judgePendingFuncNew = function(config) {
if (pendingQueue.has(`${config.method}->${config.url}`)) {
return true;
}
return false;
}
// 删除队列中对应已执行的请求
const removeResolvedFunc = function (config) {
if (pendingQueue.has(`${config.method}->${config.url}`)) {
pendingQueue.delete(`${config.method}->${config.url}`)
}
}
注意:取消需要从全局的axios中拿,如果你用了create,也需要拿导入的axios(随便你命名)如下:
// 修改了导入名称
import axiosApi from 'axios'
window.axios = axiosApi;
let pendingQueue = new Map()
// 用原本导入的axiosApi而不是后面的axios
let CancelToken = axiosApi.CancelToken;
// 响应时间
let axios = axiosApi.create({
baseURL: ''
})
番外
取消接口请求还可以运用到没有权限的情况下,是否有权限,都是通过接口判断的,如果一个页面没有权限,但是请求了多个接口,则会多个无权限提示弹窗,这种情况下,可以把后面的接口取消掉
// axios.js
window.axios = axiosApi;
let source = axiosApi.CancelToken.source();
axios.interceptors.request.use((config) => {
...
if (!config.cancelToken) {
config.cancelToken = source.token;
}
...
});
axios.interceptors.response.use(response => {
...
if (response.data.code === 1107) {
source.cancel();
// 重置token
source = axiosApi.CancelToken.source();
throw new Error(data.data.msg)
...
})