前言:由于我觉得我水平太烂了,博主建议我每天读一些源代码。恰巧作为某校园项目负责人,在参考BuildAdmin二次封装Axios后,看到众多掘金作者对无效二次封装方案的深恶痛疾,我赶紧进行code review。为了对Axios有更深入的理解,于是乎我选择了Axios作为最近阅读的源码...
axios最精彩的地方之一(我认为的)是request请求链的相关操作,在上一部分中已经进行了相关解读。但是axios还有其他功能值得我们进行分析,本章将会对取消请求、并发请求进行分析。
在上面这一段话写完后我后悔了,取消请求的细节也是十分值得注意的!
取消请求
其实在日常的使用中,我曾经遇到过使用取消请求的场景,那就是减少用户重试过多造成网络请求浪费。从axios
源码我了解了他的原理,这里通过对官网的学习,我对其进行了测试。
当没有使用AbortController
进行请求时,请求正常进行,但是当使用abort
方法后,返回请求被取消的信息。
当使用另外一种取消请求的机制cancalToken
时,能进行取消请求的同时,对取消时候信息也能自定义:
第一种取消请求的方式AbortController
是为了终止promise提供的一个对象,对比new AbortController.abort()
触发前后,其中的aborted
发出了改变,让函数起到取消请求
作用。具体的AbortController
可以查看[Mdn文档]。(AbortController.AbortController())
在abort()执行前:
执行后:
第二种方式是axios
内部提供的一系列操作,且他在一开始的axios的产生和暴露的时候已经被挂载,实例没有
相关的方法。还是老规矩,从自顶而下
从使用开始看,有这么几个步骤:
- 使用工厂方法
CancelToken
创建每一次的实例source
,如图所示,每一次创建的都是新的对象
- 给
config
传输cancelToken
的属性,其中值为source.token
- 在对应的
source
使用cancel
方法,下图也会展示执行前后的source
的内部结构的不同
两个对象是不同的:
可以看到执行前的sorce
的相关属性,其中重要的属性cancel
是函数,传入终止信息
,而token
含有promise
属性,值为pending
状态的promise
对象,如下所示:
重中之重!!!在前面提到取消请求的时候,有几个节点是判定用户是否取消请求的,而判定请求则是使用下面的这一段代码,借用思唯大哥的一幅图帮大家明确其中的几点,不管是第一种还是第二种取消请求方式,他都适用。
(上述代码出现的节点都是下面流程图中需要注意的节点)
第一种取消请求的方式源代码在实际的请求中出现,可以看到适配器封装底层请求的/lib/adapters/xhr.js
中有:
来看看第二种的源码吧!在lib/cancel/CancelToken.js
中,找到其定义:
跟随代码的步骤,俺们看看创建CancelToken
究竟做了啥,对于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;
// 创建当前promise对象,并将改变状态为Fulfiled的方法暴露
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
// 拷贝一下this
var token = this;
// 当对象的状态改变的时候,检查当前this是否含有_listeners
// 此时cancel是
// eslint-disable-next-line func-names
this.promise.then(function(cancel) {
if (!token._listeners) return;
var i;
var l = token._listeners.length;
// 如果含有_listeners属性,不打断执行其中的函数并传入此时的cancel信息
for (i = 0; i < l; i++) {
token._listeners[i](cancel);
}
// 重置this的这个属性
token._listeners = null;
});
// 相当于重写一个then方法,返回
// eslint-disable-next-line func-names
this.promise.then = function(onfulfilled) {
var _resolve;
// eslint-disable-next-line func-names
var promise = new Promise(function(resolve) {
// 注册上文提到的_listeners
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled);
promise.cancel = function reject() {
// 取消对这个的监听
token.unsubscribe(_resolve);
};
// 重写then返回的promise对象
return promise;
};
executor(function cancel(message) {
// 如果已经被取消,则返回
if (token.reason) {
// Cancellation has already been requested
return;
}
// 设置取消原因,Cancel只是格式化取消信息
token.reason = new Cancel(message);
// 把本函数属性promise编程fulfilled
resolvePromise(token.reason);
});
}
/**
* 如果传递了原因,那么抛出原因
* Throws a `Cancel` if cancellation has been requested.
*/
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
/**
*
* Subscribe to the cancel signal
*/
CancelToken.prototype.subscribe = function subscribe(listener) {
// 判定如果被取消
if (this.reason) {
// listener其实是then返回的promised resolve函数
listener(this.reason);
return;
}
// 构建相关数组
if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
};
/**
* Unsubscribe from the cancel signal
*/
CancelToken.prototype.unsubscribe = function unsubscribe(listener) {
if (!this._listeners) {
return;
}
var index = this._listeners.indexOf(listener);
if (index !== -1) {
this._listeners.splice(index, 1);
}
};
可能你看到这么长的代码会头晕,没事,我一下子也头晕!下面是一些图,希望能帮助大家进行理解。首先source
工厂不是单例
每次生产的都不是
同一个对象,而对象含有cancel
方法和token
的CancelToken
对象。
单单这样子不能明白其中的运行机制,我们再看!如果concel
代码执行了会怎样呢?
我将这一文件的代码提出来进行执行,发现他会进入此处的标记然后就终止了,那么对下面的listeners
数组的操作又是怎么回事?
和第一次请求一样,也会对其进行监听和记录:
同时也有释放函数done
对其进行清空回收:
并发请求
axios
提供了all
、spread
方法供给并发请求的执行,也许你们没有对其进行过使用,没问题,可以看下面的案例:
从源代码中看看他们的实现:
终述:axios果然是基于promsise的,他对于proise的应用玩的十分生动,不仅如此,多处体现的硬绑定也让我的基础得到了加强。我会在掘金等平台一直分享我的见解,希望能在评论区多交流学习!
我是momo,我们下期见!
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情