肆:Cancel与CancelToken

前边碰到取消请求相关的部分内容都是直接略过的,现在主线任务已经完成了,好好看一下支线任务CancelCancelToken吧。

取消请求的相关源码存放在lib/cancel目录下,我们先分析一下该目录下的几个文件,然后看一下在axios中是怎么使用他们的。

Cancel类

Cancel类的内容太简单了,构造函数接收一个message,原型对象上有一个方法和一个属性。直接贴代码吧。

1
2
3
4
5
6
7
8
9
function Cancel(message) {
this.message = message;
}

Cancel.prototype.toString = function toString() {
return 'Cancel' + (this.message ? ': ' + this.message : '');
};

Cancel.prototype.__CANCEL__ = true;

isCancel方法

isCancel方法就更简单了,在Cancel类中我们知道Cancel实例的原型对象上有一个属性__CANCEL__的值为true,就判断一下就可以了。

1
2
3
function isCancel(value) {
return !!(value && value.__CANCEL__);
};

CancelToken类

CancelToken类才是重点,我们前边在dispatchRequest等地方忽略的内容就用到了CancelToken实例的方法。

CancelToken是一个可以用于取消请求的对象

CancelToken类接收一个函数,在内部会调用这个函数并传入一个新的函数cancel作为参数。

注意CancelToken实例上会有一个属性promise,这个promisecancel函数内才会resolve

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
});
}

原型对象上有一个方法throwIfRequested。如果CancelToken实例上有reason就抛出错误抛出reason也就是Cancel实例。

1
2
3
4
5
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};

CancelToken类还有一个source方法,方法返回一个对象包含CancelToken实例和用于取消请求的方法。

1
2
3
4
5
6
7
8
9
10
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};

开发中使用CancelToken取消请求

看了CancelToken的源码肯定是有些懵的,为什么要这么做呢?

我们看一下在真正的开发过程中可以使用的一种取消请求方式:

调用source拿到取消方法,调用这个方法取消请求

1
2
3
4
5
6
7
8
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.post('xx', {
name: 'new name'
}, {
cancelToken: source.token
})
source.cancel('请勿重复提交');

再看一下另一种方式:

这个就有点像是自己实现了source方法了,最后还是调用了这个取消请求的方法

1
2
3
4
5
6
7
8
9
10
const CancelToken = axios.CancelToken;
let cancel;
axios.post('xx', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
});
// 取消请求
cancel();

现在应该对于CancelToken有一些理解了,虽然我没get到这样设计的好处。

至于为什么调用取消请求的方法就能取消请求了我们往下看哦。

在xhr中的应用

前边在dispatchRequestxhr中都略过了取消请求的部分。

dispatchRequest中就是调用了CancelTokenthrowIfRquested这个方法,就不单独说了。

看一下在xhr中的使用,大家要记得CancelToken中的promise我们还没用到呢。在xhr中有如下代码,位于XMLHttpRequest.send()之前。

1
2
3
4
5
6
7
8
9
10
11
12
13
if (config.cancelToken) {
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}

request.abort();
reject(cancel);
// Clean up request
request = null;
});
}

大家应该记得在CancelToken中执行了executor并传入了一个函数cancel。如果执行cancel这个函数就会将promiseresolve对吧,所以在我们执行了取消请求的方法之后,promsieresovle进入.then内部会通过abort方法取消正在进行的异步请求然后在xhr中的promise中调用reject抛出错误。这样请求就被取消了。

即便到现在我也不理解为什么只有axios可以使用CancelToken,但是axios.create创建的实例却不能。

就像在axios1.x版本中,通过axios.create创建的实例可以继续调用create创建实例了,但依然不能取消请求。

关于axios新版本,看时间安排尽量出一期新版本的改动内容吧。

作者

胡兆磊

发布于

2022-10-26

更新于

2022-10-27

许可协议