axios之cancelToken原理及使用
在真实项目中,当路由已经跳转,而上一页的请求还在pending状态,如果数据量小还好,数据量大时,跳到新页面,旧的请求依旧没有停止,这将会十分损耗性能,这时我们应该先取消掉之前还没有获得相应的请求,再跳转页面。
在真实项目中,当路由已经跳转,而上一页的请求还在pending状态,如果数据量小还好,数据量大时,跳到新页面,旧的请求依旧没有停止,这将会十分损耗性能,这时我们应该先取消掉之前还没有获得相应的请求,再跳转页面。这里axios给我们提供了一个方法:
基本使用
我们先来看看基本用法:
var CancelToken = axios.CancelToken;
var source = CancelToken.source();
axios.get('/user/12345', {//get请求在第二个参数
cancelToken: source.token
}).catch(function(thrown) {
});
axios.post('/user/12345', {//post请求在第三个参数
name: 'new name'
}, {
cancelToken: source.token
});
source.cancel('不想请求了'); //主动取消
注意,get请求的时候,cancelToken是放在第二个参数里;post的时候,cancelToken是放在第三个参数里。
切换页面取消
//切换页面取消请求
let source = axios.CancelToken.source();
axios.interceptors.request.use((request) => {
request.cancelToken = source.token;
return request;
});
router.then((lib) => {
lib.default.beforeEach((to, from, next) => {
source.cancel();
source = axios.CancelToken.source();
next();
});
})
取消请求之后,会进入error,在里面处理一下如果是取消请求,就不报错,
axios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (axios.isCancel(error)) {
console.log( 'request cancel ', JSON.stringify(error))
return new Promise(() =>{})
}
return Promise.reject(error)
})
实现原理
promise只有三种状态,pending(等待),resolved(成功),rejected(失败)。
它的本质就是在解决如何取消一个已经执行的promise本身。
我们可以发现,它要先引用axios.CancelToken,然后调用source()方法,会产生一个token和cancel,它的内部到底如何实现,这样做的目的是什么?
源码分析
现在我们来看看cancelToken的源码:
function Cancel(message) {
this.message = message; // 可以传递因何取消请求的message
}
function CancelToken(executor) {
// 判断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;
// 以上token现在有一个promise属性,是一个未成功的promise对象;
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason); //关键代码二: 把接收到的“取消请求信息”token.reason传递给下一个then中成功函数作为参数
});
}
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
// source 方法
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c; // 关键代码三:实现“在他处取消请求”,与上面“终止一个promise”异曲同工。
});
return {
token: token,
cancel: cancel
};
};
module.exports = CancelToken;
通过源码我们可以发现,CancelToken这个类初始化的时候需要传递一个方法executor,并且它的内部新建了一个promise,最关键的是,它把promise的resolve方法控制权放在了executor方法里面!这种操作代表什么意思?我们看一个小例子:
let resolveHandle;
new Promise((resolve)=>{
resolveHandle=resolve;
}).then((val)=>{
console.log('resolve',val);
});
resolveHandle('ok');
上面的例子中,我们用resolveHandle获取了一个promise的resolve方法的控制权,这样,我们就可以在外部控制这个promise的成功了。要知道new Promise返回的对象是无法从外部决定它成功还是失败的。
现在来看看source这个方法,我们可以看到,它new了一个CancelToken对象,并传了一个方法executor;采用相同的手法,用cancel变量将executor方法的变量c的控制权拿出来了,那么这个变量c又代表啥呢?
变量c正是我们前面说到的在CancelToken初始化时,传入executor方法的,也即:
function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
}
也就是说cancel代表的是上面的这个方法,有了这个方法,就可以在外部控制CancelToken内部的promise对象了。
在source方法中,除了cancel,还有一个token,这个token是CancelToken类的一个实例,可以访问到内部的promise。
因此CancelToken类如此封装的主要目的就是为了能够分离promise和resolve方法,让用户可以自己调用resolve方法。一旦resolve后,就会触发promise的then方法,现在看看内部promise后的then方法是什么:
// 在发送请求之前,验证了cancelToken,看来此处就是用来取消请求的;
if(config.cancelToken){
// 具体是如何取消的,是在这个判断内定义的;
config.cancelToken.promise.then(function(cancel){
request.abort();
reject(cancel);
request = null;
})
}
// 发送请求
request.send(requestData);
上面是xhr.js的关于cancelToken部分相关代码,可以看到,当用户调用cancel后,就会立即执行abort方法取消请求,同时调用reject让外层的promise失败。