使用方法
方式一
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
}
});
source.cancel('Operation canceled by the user.');
方式二
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
});
cancel();
源码分析
在axios/lib/axios.js
中定义了跟取消相关的方法
var axios = createInstance(defaults);
...
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
...
我先看CancelToken,进入到axios/lib/cancel/CancelToken.js
文件
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(cancel方法);
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
//token.reason是Cancel的实例
token.reason = new Cancel(message);
resolvePromise(token.reason);//改变promise的状态
});
}
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token, //CancelToken的实例
cancel: cancel //function cancel(message) {...}
};
};
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
CancelToken构造函数,定义了一个promise,状态一直是padding,将决定状态的resolve方法传递cancel方法内部,cancel方法作为参数传到外部executor方法中。
CancelToken.source实际上new 一个CancelToken的实例,上述的cancel方法传给cancel变量
进入到axios/lib/cancel/Cancel.js
文件
function Cancel(message) {
this.message = message;
}
Cancel.prototype.toString = function toString() {
return 'Cancel' + (this.message ? ': ' + this.message : '');
};
Cancel.prototype.__CANCEL__ = true;
Cancel构造函数,有属性message,Cancel.prototype有属性__CANCEL__
进入到axios/lib/cancel/isCancel.js
文件
function isCancel(value) {
return !!(value && value.__CANCEL__);
};
思考
如何一步步实现取消的?
function throwIfCancellationRequested(config) {
//判断是否配置有cancelToken,
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
}
function dispatchRequest(config) {
throwIfCancellationRequested(config);//请求前
...
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);//请求后,resolve
...
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) { //请求后,reject
throwIfCancellationRequested(config);
...
});
}
调用throwIfCancellationRequested方法,内部实现先判断是否配置有cancelToken,则调用CancelToken.prototype.throwIfRequested,如果调用cancel操作过了肯定reason存在,抛出throw reason,reason实际上是Cancel的实例
在axios/lib/adapters/xhr.js
中
function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
...
if (config.cancelToken) {
//请求中,监听cancelToken中promise状态改变
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
request = null;
});
}
...
}
}
总结: CancelToken.source().token实际可用理解为new 一个promise,状态一直是 padding。把可用改变状态方法传到cancel方法中。外部调用cancel实现取消请求。
cancel操作,其实是执行resolvePromise,让cancelToken中promise状态变为已完成。实际上在发送请求前、请求中,请求后resolve、请求后reject四部分,都会去判断是否有cancel操作。
1、发送请求前,如果有cancel操作,说明reason存在,则throw reason, reason对象(实际是Cancel的实例)
2、请求中,如果有cancel操作,说明已经建立xhr,则要abort这个请求,所以要再xhr中有config.cancelToken.promise.then等待promise状态改变
3、请求后,如果有cancel操作,与发送请求前处理方式一样