JS魔法堂: Native Promise Only源码剖析
一, 前言
深入学习Promise的朋友应该都看过<深入理解Promise五部曲>这一系列的文章, 以解除回调地狱之外的观点来剖析Promise更多的内涵,确实十分精彩.
Part 1: The Sync Problem(译文:http://segmentfault.com/blog/kk_470661/1190000000586666)
Part 2: The Inversion Problem(译文:http://segmentfault.com/blog/kk_470661/1190000000591382)
Part 3: The Trust Problem(译文:http://segmentfault.com/blog/kk_470661/1190000000593885)
Part 4: The Extension Problem(译文:http://segmentfault.com/blog/kk_470661/1190000000600268)
Part 5: The LEGO Problem(译文:http://segmentfault.com/blog/kk_470661/1190000000611040)
NPO(Native Promise Only)是原文作者polyfill的ES6 Promise, 本文为拜读文章及源码后的笔记,以便日后查阅.
二, 整体脉络
对于Promise实现而言, 主要的主体类型就两个-----Promise和Thenable. NPO中通过MakeDef构建Promise的内部状态结构体def, 并且通过def.chain存储Promise子节点P2-1,P2-2到P2-n, 从而形成一颗Promise树. 而Thenable的内部状态结构体def_wrapper则由MakeDefWrapper构建而成.
Promise树的结构并不稳定, 实际上每个Promise节点仅与状态为pending的子节点关联, 一旦子节点状态发生变化则断开关联.(该部分在 notify()
中实现)
{Promise} then(success, failure)
, 将success和failure事件处理函数与新生成的Promise子节点绑定, 但订阅的是Promise父节点的状态变化事件.
另外NPO中通过构建一个异步执行请求队列(scheduling_queue),来收集异步执行请求然后對请求作同一处理,并通过门闩(cycle)来防止重复执行异步请求处理操作.
三, 源码详解
先看看Promise构造函数, 规定仅能通过new方式来构建Promise实例.
function Promise(executor) {
if (typeof executor != "function") {
throw TypeError("Not a function");
}
if (this.__NPO__ !== 0) {
throw TypeError("Not a promise");
}
// instance shadowing the inherited "brand"
// to signal an already "initialized" promise
this.__NPO__ = 1;
// 内部结构体
var def = new MakeDef(this);
this["then"] = function then(success,failure) {
var o = {
success: typeof success == "function" ? success : true,
failure: typeof failure == "function" ? failure : false
};
// Note: `then(..)` itself can be borrowed to be used against
// a different promise constructor for making the chained promise,
// by substituting a different `this` binding.
o.promise = new this.constructor(function extractChain(resolve,reject) {
if (typeof resolve != "function" || typeof reject != "function") {
throw TypeError("Not a function");
}
o.resolve = resolve;
o.reject = reject;
});
// 构建Promise树
def.chain.push(o);
// 当前Promise节点状态不为pending时,发起异步执行请求事件处理函数
if (def.state !== 0) {
schedule(notify,def);
}
return o.promise;
};
this["catch"] = function $catch$(failure) {
return this.then(void 0,failure);
};
try {
// 调用工厂方法
executor.call(
void 0,
function publicResolve(msg){
resolve.call(def,msg);
},
function publicReject(msg) {
reject.call(def,msg);
}
);
}
catch (err) {
reject.call(def,err);
}
}
Promise的状态变化放在resolve和reject函数中
function resolve(msg) {
var _then, def_wrapper, self = this;
// already triggered?
if (self.triggered) { return; }
self.triggered = true;
// unwrap
if (self.def) {
self = self.def;
}
try {
if (_then = isThenable(msg)) {
// 构造Thenable的内部状态结构体
def_wrapper = new MakeDefWrapper(self);
_then.call(msg,
function $resolve$(){ resolve.apply(def_wrapper,arguments); },
function $reject$(){ reject.apply(def_wrapper,arguments); }
);
}
else {
self.msg = msg;
self.state = 1;
if (self.chain.length > 0) {
schedule(notify,self);
}
}
}
catch (err) {
reject.call(def_wrapper || (new MakeDefWrapper(self)),err);
}
}
function reject(msg) {
var self = this;
// already triggered?
if (self.triggered) { return; }
self.triggered = true;
// unwrap
if (self.def) {
self = self.def;
}
self.msg = msg;
self.state = 2;
if (self.chain.length > 0) {
schedule(notify,self);
}
}
下面看一下我觉得最亮眼的地方异步执行请求队列, 主要由以下几个部分组成
1. notify, 遍历def.chain中的所有Promise子节点, 最后由于所有Promise子节的状态均变为fulfilled或rejected因此清空def.chain.
2. notifyIsolated, 被notify所调用, 用于单独调用绑定在每个Promise子节点的success或failure事件处理函数, 并修改Promse子节点的状态.
3. scheduling_queue, 存放异步执行请求(以链表实现, 對队列首尾操作性能比数组高).
4. schedule, 向异步执行请求队列添加元素, 并发起异步请求处理操作.
上述的1和2两点将作为异步执行请求被存放在3中.代码中各部分则通过4来對队列和异步执行请求作操作.
function notify() {
for (var i=0; i<this.chain.length; i++) {
notifyIsolated(
this,
(this.state === 1) ? this.chain[i].success : this.chain[i].failure,
this.chain[i]
);
}
this.chain.length = 0;
}
// NOTE: This is a separate function to isolate
// the `try..catch` so that other code can be
// optimized better
function notifyIsolated(self,cb,chain) {
var ret, _then;
try {
if (cb === false) {
chain.reject(self.msg);
}
else {
if (cb === true) {
ret = self.msg;
}
else {
ret = cb.call(void 0,self.msg);
}
if (ret === chain.promise) {
chain.reject(TypeError("Promise-chain cycle"));
}
else if (_then = isThenable(ret)) {
_then.call(ret,chain.resolve,chain.reject);
}
else {
chain.resolve(ret);
}
}
}
catch (err) {
chain.reject(err);
}
}
scheduling_queue = (function Queue() {
var first // 指向队首元素
, last // 指向队尾元素
, item;
function Item(fn,self) {
this.fn = fn;
this.self = self;
this.next = void 0;
}
return {
// 元素入队
add: function add(fn,self) {
item = new Item(fn,self);
if (last) {
last.next = item;
}
else {
first = item;
}
last = item;
item = void 0;
},
// 清空队列
drain: function drain() {
var f = first;
first = last = cycle = void 0;
// 从队首元素开始遍历所有队列元素
while (f) {
f.fn.call(f.self);
f = f.next;
}
}
};
})();
// 安排执行状态变化事件的处理函数
function schedule(fn,self) {
scheduling_queue.add(fn,self);
// 防止重复发起异步执行请求
if (!cycle) {
cycle = timer(scheduling_queue.drain);
}
}
四, 总结
尊重原创,转载请注明
- 自适应页面高度
- 用CSS制作可交换带事件处理的图片按钮
- tomcat相关配置技巧梳理
- 用Layer.search快速查询图元
- Ado.Net连接池的速度测试
- Excel小账本使用说明
- Enterprise Library 4.1学习笔记8----缓存应用程序块之FileDependency
- CSS实现按钮的两张图片的同步出现
- 完整部署CentOS7.2+OpenStack+kvm 云平台环境(6)--在线调整虚拟机的大小
- 关于vb中的容器
- 关于vb中的容器
- Mysql数据库之Binlog日志使用总结
- 揭秘新人机大战柯洁对手天壤 AI排名已力压Deepzen
- 一路走到java工程师,java都快出java9了,到底该如何学java?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- git文件管理心得分享
- Linux如何实现断点续传文件功能
- Xshell实现Windows上传文件到Linux主机的方法
- Linux中的EXT系列文件系统格式详解
- Linux中hexdump命令用法
- CentOS7挂载新数据盘的完整步骤
- Linux下安装Keepalived及原理分析
- centos6搭建gitlab的方法步骤
- Linxu服务器上安装JDK 详细步骤
- linux 不改变目录结构移动 home 目录到新分区的操作方法
- ubuntu14.04安装opencv3.0.0的操作方法
- Linux中让alias设置永久生效的方法详解
- Centos系统下“无法打开并写入文件”问题的解决
- 如何在Linux下设置录音笔时间
- Linux下ZooKeeper分布式集群安装教程