1.前言
之前看到Axios的取消请求,不像其他的API那么直观好理解,有两种取消请求的方式,感觉很奇怪,所以决定去研究下源码,研究完之后才发现原来如此!
2.源码分析
首先第一种写法的代码:
let Http = this.$http,
CancelToken = Http.CancelToken,
that = this;
Http.post(
"https://www.fastmock.site/mock/257d9fdebd0b1dd887acd6ec80db8ade/cena/post/test",
{},
{
cancelToken: new CancelToken((e) => {
that.cancel = e;
}),
}
)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
if (Http.isCancel(error)) {
//Request canceled 手动取消请求
console.log("Request canceled", error.message);
} else {
///.....
}
});
this.cancel("手动取消请求");
既然看源码那么就得从入口开始看,CancelToken
是axios
的一个属性,首先找到axios.js
文件
再找到CancelToken.js
文件,先不要看内部细节,先看整体结构,发现CancelToken
就是个构造函数,构造函数上有个source
方法,构造函数原型上有throwIfRequested
,subscribe
,unsubscribe
等方法!
再来看下上面写的这段代码:
{
cancelToken: new CancelToken((e) => {
that.cancel = e;
}),
}
new CancelToken
不就是创建实例吗?再来看下CancelToken
这个构造函数里面做了啥操作!
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;
// eslint-disable-next-line func-names
this.promise.then(function(cancel) {
//....
});
// eslint-disable-next-line func-names
this.promise.then = function(onfulfilled) {
//....
};
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
从大结构上来看,CancelToken
构造函数只有一个promise
属性(先不管它是干什么,后面会说),再看executor
是什么?不就是new CancelToken
的参数,是个function
! 执行executor
相当下面的代码:
that.cancel = function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
} ;
好了,执行that.cancel
方法就是取消请求的关键了!我们再回到上面说的CancelToken
构造函数只有一个promise
属性!
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
看上面代码意思是把resolve
赋值给了一个resolvePromise
的变量,大家都知道Promise
当执行到resolve
方法时候,就会执行then
里面的回调函数,这里的意思相当把resolve
暴露出去,让外面来决定什么时候来执行Promise
的then
里面的回调函数,这个思路很巧妙!当执行that.cancel
的时候,先会去判断有没有创建Cancel
实例,有的话就直接退出函数,没有的话就创建Cancel
实例,并且触发Promise
的then
里面的回调函数,先来看下Cancel是什么东西!其实代码非常简单,其实就是设置下message
,__CANCEL__
属性,只要创建了实例,__CANCEL__
必须为true!
'use strict';
/**
* A `Cancel` is an object that is thrown when an operation is canceled.
*
* @class
* @param {string=} message The message.
*/
function Cancel(message) {
this.message = message;
}
Cancel.prototype.toString = function toString() {
return 'Cancel' + (this.message ? ': ' + this.message : '');
};
Cancel.prototype.__CANCEL__ = true;
module.exports = Cancel;
接下来是执行Promise
的then
里面的回调函数了,这里的cancel
形参就是token.reason
(Cancel的实例
)!
this.promise.then(function(cancel) {
if (!token._listeners) return;
var i;
var l = token._listeners.length;
for (i = 0; i < l; i++) {
token._listeners[i](cancel);
}
token._listeners = null;
});
好了接下来,看xhr.js
里面的代码,我只列主要一些核相关代码,看下面代码:
var request = new XMLHttpRequest();
request.onabort = function handleAbort() {
if (!request) {
return;
}
reject(createError('Request aborted', config, 'ECONNABORTED', request));
// Clean up request
request = null;
};
//省略代码
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = function(cancel) {
if (!request) {
return;
}
reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel);
request.abort();
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
首先判断配置里面有没有cancelToken
属性,因为这个属性是取消请求属性(一定会有),
接着定义了一个onCanceled
函数(没执行),注意request
是异步对象,request.abort()
执行取消请求并清空异步对象!那么onCanceled
函数只要执行了就取消请求了!接下往下看执行了下面这个方法:
config.cancelToken && config.cancelToken.subscribe(onCanceled);
我们再回到CancelToken.js
的 CancelToken
原型上的subscribe
方法做了什么?
CancelToken.prototype.subscribe = function subscribe(listener) {
if (this.reason) {
listener(this.reason);
return;
}
if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
};
主要看下面的if判断,如果没有this._listeners
,那么this._listeners = [listener];
那么是不是this._listeners=[onCanceled]
,那么再回到上面的Promise
的then
里面的回调函数,看下面代码!
this.promise.then(function(cancel) {
if (!token._listeners) return;
var i;
var l = token._listeners.length;
for (i = 0; i < l; i++) {
token._listeners[i](cancel);
}
token._listeners = null;
});
最主要看token._listeners[i] (cancel)
这行代码,相当执行下面代码!
(function(cancel) {
if (!request) {
return;
}
reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel);
request.abort();
request = null;
})(new Cancel(message))
第一种代码分析就这样了,再看下第二种写法:
let Http = this.$http,
CancelToken = Http.CancelToken;
this.source =CancelToken.source();
Http.post(
"https://www.fastmock.site/mock/257d9fdebd0b1dd887acd6ec80db8ade/cena/post/test",
{},
{
cancelToken: this.source.token
}
)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
if (Http.isCancel(error)) {
console.log("Request canceled", error.message);
} else {
///
}
});
this.source.cancel("手动取消请求");
这里用到了CancelToken
构造函数上的source
方法!我们来看下源码:
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
这个方法相当做了一层封装,它返回出来是一个对象,token是CancelToken的实例,cancel是用来取消请求的方法
,内部的原理和第一种方法是一样的!