首发于我的公众号「前端面壁者」,欢迎关注。
基于 TC39 的 cancelable promises proposal 提议封装,但是这个提议已经被发起人自己取消了,据说是因为 Google 内部反对意见很大,详情可以到相关 issueWhy was this proposal withdrawn?看一下。
axios
版本 v0.24.0
通过 github1s
网页可以 查看 axios 源码
调试需要 clone
到本地
git clone https://github.com/axios/axios.git
cd axios
npm start
http://localhost:3000/
cancel
模块包含三个文件Cancel
、CancelToken
、isCancel
├─cancel // 取消请求
│ Cancel.js
│ CancelToken.js
│ isCancel.js
A
Cancel
is an object that is thrown when an operation is canceled.
当操作被取消时会抛出一个Cancel
对象
"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;
Cancel
类toString
,一个实例属性__CANCEL__
__CANCEL__
被设定为 true
,是 Cancel 类的标志Tips:我们知道一个请求会有三种状态请求前
、请求中
、请求结束
,取消动作可能发生在其中的任何一个阶段。不同阶段对应的取消逻辑在axios
中是不一样的,且在请求中
这个阶段,函数执行的逻辑位置也不一样,所以当我们发起取消请求时,axios
需要一种机制获知当前网络请求走到了哪个阶段的哪个位置才能执行对应的取消和返回逻辑,Cancel
类就是这样一个机制。
"use strict";
module.exports = function isCancel(value) {
return !!(value && value.__CANCEL__);
};
Cancel
的实例属性__CANCEL__
来判断value
是否为 Cancel
实例isCancel
函数,如果入参 val
不是 Cancel
类会返回 false
,否则返回 true
A
CancelToken
is an object that can be used to request cancellation of an operation.
CancelToken
是被用于取消请求操作的类
参考文章
林景宜的记事本
提到这是一个让人一头雾水
的类,刚看的时候笔者也有同感,虽然经过一些剖析笔者已经分析的尽可能详细(自我感觉良好 ),但在观看之前还是推荐不了解设计模式的小伙伴去了解一下其中的发布/订阅
模式,会有助于理解
"use strict";
var Cancel = require("./Cancel");
两段
this.promise.then
并不是取消逻辑,暂时先忽略
/**
* 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;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
// eslint-disable-next-line func-names
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;
});
// 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) {
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled);
promise.cancel = function reject() {
token.unsubscribe(_resolve);
};
return promise;
};
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
接下来按行分析
1. 首先判断入参executor
是否是 function 类型,如果不是会抛出类型错 TypeError
executor
是一个函数?CancelToken
通过发布订阅模式来实现传递取消信息,订阅者把自己想订阅的事件cancel/c
注册到调度中心CancelToken
,当适配器返回信息时发布者发布到调度中心CancelToken
,调度中心再统一调度执行订阅者注册到调度中心的处理代码cancel
。这也就理解了这个函数为什么叫executor-执行器
。2. 创建全局变量resolvePromise
resolve
便于后续的链式调用,且只有全局变量才能穿过函数作用域
拿到 resolve
值3. 使用new Promise()
创建函数promiseExecutor
的实例对象,表明executor
是一个异步函数,其返回值resolve
传递给全局变量resolvePromise
,调用时可使用this.promise.then()
获取返回值resolve
或直接使用resolvePromise
promise
对象?request.abort()
在适配器xhr.js
或http.js
里触发,这是一个异步的方法,当这个动作发生后我们才能在 resolve
中拿到返回值4. 创建全局变量token
表示当前CancelToken
的实例
5. executor
执行器执行订阅者事件cancel
,该方法入参message
来自用户调用时传参
6. 订阅者事件cancel
在执行时首先会判断实例token
是否存在reason
属性值,如果存在直接返回,否则使用new Cancel()
声明一个 cancel 类给reason
赋值
new Cancel()
声明一个 cancel 类给reason
赋值?reason
的原型链上会挂载一个__CANCEL__
表示 cancel 类,同时配合resolvePromise
执行会把this.promise
实例状态改为fulfilled
,这意味着是用户主动取消请求返回的信息而非因为其他异常返回(会把this.promise
实例状态改为reject
)CancelToken
构造函数就是一个发布订阅函数,通过发布订阅触发向外 resolve
或者抛出错误(reject
),Promise
链式结构成功拿到resolve
值或者 catch
到错误后,会返回用户的输入message
或停止继续执行并执行错误回调。
/**
* Throws a `Cancel` if cancellation has been requested.
*/
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
/**
* Returns an object that contains a new `CancelToken` and a function that, when called,
* cancels the `CancelToken`.
*/
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel,
};
};
CancelToken
对象 token
和函数 cancel
c
注册到调度中心CancelToken
,调度中心在执行器executor
中立即执行订阅者事件cancel
函数通过全局作用域
拿到c
,通过 return
暴露给用户一个订阅事件的入口/**
* Subscribe to the cancel signal
*/
CancelToken.prototype.subscribe = function subscribe(listener) {
if (this.reason) {
listener(this.reason);
return;
}
if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
};
cancel
被执行时,会向当前实例的_listeners
属性上追加链式调用的返回值/**
* 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);
}
};
cancel
被执行时,如果是重复取消就移除当前实例的_listeners
属性上对应位置的链式调用1. 仙凌阁
的文章详细 Axios 源码解读
2. 林景宜
的文章林景宜的记事本 - Axios 源码解析(二):通用工具方法
3. 若川
的文章学习 axios 源码整体架构,打造属于自己的请求库
4. 杰凌
的文章深入解读 axios 源码
5. 海角在眼前
的文章设计模式(三):观察者模式与发布/订阅模式区别