需求场景,由于某些请求很耗时,比如大文件上传、下载,当用户在当前页面发起请求后,又不想等待了,想去其他页面看看。如果不取消此类请求,就会在下一个页面莫名其妙地弹出成功或失败结果,也会影响页面响应。
其实,要不要取消前一个页面的请求是根据实际情况确定的,如果希望在浏览其他页面时文件继续上传,就不能取消。现在,我们只讨论下需要取消上个页面请求的代码实现:
一、看下axios官网的介绍:
可以使用 CancelToken.source
工厂方法创建 cancel token,像这样:
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.');
还可以通过传递一个 executor 函数到 CancelToken
的构造函数来创建 cancel token:
const CancelToken = axios.CancelToken; let cancel; axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { // executor 函数接收一个 cancel 函数作为参数 cancel = c; }) }); // cancel the request cancel();
可以看出,取消请求需要两个步骤,一是请求配置设置cancelToken,二是取消时调用cancel方法。
官网提供的两种方式,第一种通过CancelToken类的source方法,返回具体的cancelToken和对应的cancel方法;第二种通过CancelToken的构造函数new CancelToken(executor),返回具体的cancelToken,同时在executor回调中获得cancel方法。
二、查看axios源码:
对应第一种方式:可以看出source方法内部还是调用了CancelToken的构造函数,最后将token和对应的cancel封装成一个对象返回。
/** * Returns an object that contains a new `CancelToken` and a function that, when called, * cancels the `CancelToken`. */ CancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); return { token: token, cancel: cancel }; };
对应第二种方式:可以看出 构造函数会将cancel方法当做参数传递给executor的回调函数,所以我们才可以在回调中获得cancel方法。
/** * A `CancelToken` is an object that can be used to request cancellation of an operation. * * @class * @param {Function} executor The executor function. */ function CancelToken(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); } var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); var token = this; executor(function cancel(message) { // 关键代码 if (token.reason) { // Cancellation has already been requested return; } token.reason = new Cancel(message); resolvePromise(token.reason); }); }
三、实现我们的代码
① 在请求拦截器处,设置cancelToken,并且将cancel方法绑定在Vue原型的$httpRequestList 数组上,在这里我还用MAX_CANCEL_TOKEN_NUM 对保存的cancel方法数量做了限制。
const CANCEL_TOKEN = axios.CancelToken; const MAX_CANCEL_TOKEN_NUM = 2000; Vue.prototype.$httpRequestList = []; axios.interceptors.request.use( config => { config.CancelToken = new CANCEL_TOKEN(c => { if (Vue.prototype.$httpRequestList.length == MAX_CANCEL_TOKEN_NUM) { Vue.prototype.$httpRequestList.pop(); } Vue.prototype.$httpRequestList.unshift(c); }) return config; }, err => { return Promise.reject(err); });
② 路由切换时,遍历 Vue.prototype.$httpRequestList 执行每一个cancel方法。 是不是放在afterEach方法中有待斟酌。。。
/* 全局后置钩子 */ router.afterEach(function (to, from) { Vue.prototype.$httpRequestList.forEach(cancel => { cancel() }); });
参考:http://www.axios-js.com/zh-cn/docs/#取消
https://github.com/axios/axios/blob/master/lib/cancel/CancelToken.js