axios 使用 cancel token 取消请求
业务场景
1. 在项目中切换路由,停止原路由中的正在请求的接口
在我参与的项目中有的页面在进入时会调用多个接口,并且这些接口可能会几秒后才请求完;在用户切换路由时这些接口就没有必要再请求了;
如果需求只是要在切换路由是取消之前的全部接口,使用下面的方法就可以实现;如果还有取消具体的某一个接口的需求,那么请看业务场景2的实现方式;
实现代码如下:
// 使用 CancelToken.source
// CancelToken.source 方法会构建一个 CancelToken 对象,并返回一个有两个参数的对象( token、cancel )
// token -> 构建出的 CancelToken 对象
// cancel -> 取消请求需要调用的方法
// 由于 CancelToken 对象是在拦截器外构建的,所有的接口中的 config.cancelToken 指向的都是同一个 CancelToken 对象,所以可以使用 source.cancel 方法取消所有的接口
import $store from '../store/index'; // 引入 store
let CancelToken = axios.CancelToken;
let source = CancelToken.source();
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
config.cancelToken = source.token
// 此处使用 store 存储取消接口的方法,便于在其他地方调用
let arr = $store.getters.getCancelTokenList
arr.push(source.cancel)
$store.commit('setCancelTokenList', arr)
// 我见过有人在 main.js 中直接声明变量( Vue.prototype.$sourceCancel = null ) ,拦截器中进行赋值,再在需要的地方调用
// 不知道这种方法的好处或者坏处,如果有人了解,欢迎评论
// $sourceCancel = source.cancel
return config;
}, function (error) {
// 预处理请求有异常(error)时抛出错误
return Promise.reject(error);
});
// router.js
// 在路由守卫中设置切换路由后取消接口,并将存储方法的变量清空
router.beforeEach((to, from, next) => {
console.log(this.$store.getters.getCancelTokenList[0])
this.$store.getters.getCancelTokenList[0]('取消接口')
$store.commit('setCancelTokenList', [])
next()
})
2. 多次调用同一个接口时,需要取最新的接口返回信息
实现思路:取消接口的方法与场景1中的大致相同,但是由于需要对具体的某个接口进行取消,所以需要修改一下存储取消接口方法的变量,如下:
// 使用请求的 url 地址作为变量的 key 存储取消接口的方法
CancelTokenList = {
url: function() {
}
}
import $store from '../store/index'; // 引入 store
let CancelToken = axios.CancelToken;
let cancel = null
// 方法1 - 使用 CancelToken.source
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 将 CancelToken 对象的构建放在拦截器里面,就不会出现多个取消方法指向同一个对象
let source = CancelToken.source();
let obj = $store.getters.getCancelTokenList
let url = config.url
config.cancelToken = source.token
if (config.method === 'get') {
url = url.split('?')[0]
}
if (obj[url]) {
// 在存储的对象中有当前 url 的变量,则调用方法取消请求
obj[url]('取消接口')
delete obj[url]
}
obj[url] = source.cancel
$store.commit('setCancelTokenList', obj)
return config;
}, function (error) {
// 预处理请求有异常(error)时抛出错误
return Promise.reject(error);
});
// 方法2 - 直接使用 CancelToken 的构造函数
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
let url = config.url
let obj = $store.getters.getCancelTokenList
if (config.method === 'get') {
url = url.split('?')[0]
}
if (obj[url]) {
// 在存储的对象中有当前 url 的变量,则调用方法取消请求
obj[url]('取消接口')
delete obj[url]
}
config.cancelToken = new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
obj[url] = cancel
$store.commit('setCancelTokenList', obj)
return config;
}, function (error) {
// 预处理请求有异常(error)时抛出错误
return Promise.reject(error);
});
// router.js
// 使用 url 作为 key 的方式存储取消方法的话在路由守卫中就需要进行一次遍历
router.beforeEach((to, from, next) => {
let obj = this.$store.getters.getCancelTokenList
for (let key in obj) {
obj[key]('取消接口')
}
$store.commit('setCancelTokenList', [])
next()
})
问题
后台在写接口时可能会有如下两种写法:
let id = 123
axios.get('/test?id=' + id, {
cancelToken: source.token
}).catch(function(thrown) {
console.log('thrown', thrown);
});
axios.get('/test/' + id, {
cancelToken: source.token
}).catch(function(thrown) {
console.log('thrown', thrown);
});
在上面的实现方法中,我使用 url 作为 key 的形式进行遍历的存储时,如果是第二种形式的接口,会导致 url 无法作为唯一标识来使用,希望能有大佬有解决方法