本篇继续学习Promise的实现思路,这一次从github上找了一个比较简单的库来学习:then/promise。 它代码比较少,如果对Promise特性掌握地比较熟悉的话,学起来会比较容易。
Promise的特性及简单实现思路可参考之前的两篇文章:
ES6 Promise;
从别人博客学到的基本的Promise实现思路
这个库的核心代码是这个文件core.js,本篇也是主要学习这个文件的实现要点。
核心实现思想
我觉得每个Promise实现库最重要的一个思路是要解决then的调用对象、then的回调函数以及then的返回对象三者之间的作用关系,在上一篇文章:从别人博客学到的基本的Promise实现思路,介绍的方法是通过一个中间形式的回调函数,在这个中间层的回调函数内,利用js闭包的特性,来联结它们三个角色。 then/promise这个库,它思路跟中间形式的回调函数差不多,它采用的是面向对象的形式,封装了一个临时的Handler对象:1
2
3
4
5function Handler(onFulfilled, onRejected, promise){
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
这个Handler对象接收三个参数,前面两个是then方法调用时的两个回调函数,而第三个参数则是then方法返回的那个promise实例对象。现在Handler对象已经联结了then方法的回调和then方法返回的Promise实例,还需要再联结到then方法的调用对象才行。
这个库是这么做的,它在Promise类里面,封装了一个_deferreds属性:1
2
3
4
5
6
7
8
9
10
11
12
13
14function Promise(fn) {
if (typeof this !== 'object') {
throw new TypeError('Promises must be constructed via new');
}
if (typeof fn !== 'function') {
throw new TypeError('Promise constructor\'s argument is not a function');
}
this._deferredState = 0;
this._state = 0;
this._value = null;
this._deferreds = null;
if (fn === noop) return;
doResolve(fn, this);
}
经过then方法的调用之后,then内部创建的Handler对象,会存储到then调用对象的_deferreds属性上面去。等到then的调用对象被resolve或reject的时候,会同时拿到_deferreds属性上存储的所有Handler对象,并挨个地把Handler对象上的回调函数都执行一遍,并改变Handler对象上的Promise实例的状态。
这样一来,它就实现了then的调用对象、then的回调函数以及then的返回对象三者之间的联动关系。
下面开始解读它的源码。 这个core.js总共不到200行,你不看我下面的内容也没什么关系,每个人理解的思路,描述的方法都不一定能帮助另外一个人快速理解,如果感兴趣的话,自己去反复阅读它的源码,效果会更好。
源码零散的部分
1 | var asap = require('asap/raw'); |
第一个asap的依赖,是为了解决Promise跟event-loop之间的问题,从其它一些资料学习到,Promise是一种mico-task,它的回调是异步执行的,会在本轮事件循环的末尾执行,而setTimeout这种都是下一轮事件循环的开始执行。asap这个模块可以让Promise回调在本轮事件循环的末尾执行,这是一个npm 模块,可以去它的主页学习。
Promise的构造函数
1 | function Promise(fn) { |
doResolve核心方法
Promise都是直接new Promise(function(resolve, reject){})这么用的,它的构造函数接收一个参数函数,这个参数函数我们用来写异步任务,它接收两个参数,分别是resolve和reject,利用这两个参数,可以改变Promise实例的状态。doResolve方法是一个单独的方法,它不与任何的Promise实例关联,你可以认为它是静态的,它的第一个参数fn表示一个包含异步任务的函数,通常是Promise构造函数参数,它的第二个参数promise表示一个跟fn相关的promise实例,fn内部的逻辑最终改变的就是这个promise实例的状态。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25function doResolve(fn, promise) {
var done = false;
// 这里已经可以看到tryCallTwo这几个方法的作用了
var res = tryCallTwo(fn,
function (value) {
if (done) return;
done = true;
//核心方法
resolve(promise, value);
},
function (reason) {
if (done) return;
done = true;
//核心方法
reject(promise, reason);
});
//如果前面通过tryCallTwo调用fn发生错误,下面的代码就会执行,把promise给reject掉
//这就是Promise标准里面的特性:当new Promise发生错误,那么构造出的Promise实例就是rejected
if (!done && res === IS_ERROR) {
done = true;
reject(promise, LAST_ERROR);
}
}
注意tryCallTwo的第二个参数和第三个参数的含义,假如有:1
new Promise(function(resolve, reject){})
这个里面的resolve和reject就是tryCallTwo的第二个参数和第三个参数!done变量的作用是为了防止fn,也就是Promise构造函数的参数内部,多次调用resolve或reject的情况,比如这种:1
2
3
4
5
6new Promise(function(resolve, reject){
resolve();
resolve();
resolve();
resolve();
});
doResolve内部的resolve方法和reject方法,又是这个库的另外两个核心方法,它们跟doResolve方法一样,没有与任何Promise实例绑定,可以认为是静态方法。
resolve核心方法
这个方法接收2个参数,第一个参数self表示一个promise实例,第二个参数表示一个状态,这个方法的作用就是尝试用第二个参数把第一个参数给resolve掉。
1 | function resolve(self, newValue) { |
reject核心方法
这个方法跟resolve类似,就是为了把第一个参数表示的promise实例,用第二个参数给reject掉。1
2
3
4
5
6
7
8function reject(self, newValue) {
self._state = 2;
self._value = newValue;
if (Promise._onReject) {
Promise._onReject(self, newValue);
}
finale(self);
}
它比较简单,没有复杂逻辑,直接就是reject。
final核心方法
1 | function finale(self) { |
只有三种情况会调用finale方法:
- self状态为resolved
- self状态为rejected
- self状态为3
这个方法内部通过handle方法来处理这三个场景的逻辑。
then方法
1 | Promise.prototype.then = function(onFulfilled, onRejected) { |
在这个core.js内,并没有提到过catch实例方法,因为只要then方法实现好了,catch方法自然能实现。它的then方法,有一个safeThen的逻辑,这个是为了应付一些特殊的场景的,比如说别的一个Promise类的实例可能借用了这个库的then实例方法:1
Promise.prototype.then.call(objOfAnotherPromiseLibrary, onFulfilled, onRejected)
经验有限,我也不太清楚它这个逻辑真正的价值,所以在学习这个库的时候我没有花太多时间在这个点上。
通过源码,可以看到then方法很简单,比我上篇笔记中写的then方法简单多了,它把最主要的逻辑都放在了handle里面来处理了。另外也能看到,then方法内部,实例化了一个Handler对象,联结了then内部返回的Promise实例以及then方法的两个回调函数,这个Handler实例,就是其它代码中经常看到用来表示deferred含义的对象。 “延迟对象”
handle核心方法
1 | // 纳闷了,为啥参数这里叫deferred,外面的类叫Handler |
这个hande方法要解决四个场景的作用:
- self这个promise状态为pending的时候
在这个场景下,handle方法的作用是把deferred这个对象缓存到self实例的_deferreds属性上。 - self这个promise状态为1的时候
在这个场景下,它直接跳到最后的handleResolved方法,通过这个方法,来完成deferred上面的回调函数调用和promise实例的状态处理。 - self这个promise状态为2的时候
同第2点。 - self这个promise状态为3的时候
有2个情况会出现这个场景,一是new Promise的构造参数内,通过resolve方法,传递了另外一个Promise实例;二是then方法的回调函数返回了一个新的Promise实例。这两种场景会让promise实例们通过self._value和self._state=3构造成为一个链表,这个方法内部先通过while结构,找到这个链表最原始的那个promise实例,并把它赋值给原来的self变量。然后再重新根据self的状态来判断进行前面第1、2、3点处理。
handleResolve核心方法
1 | function handleResolved(self, deferred) { |
这个方法是一定只有在self的状态变为1或2的时候才会调用的,deferred也一定是self对象上保存的Handler类的实例,deferred上面缓存着曾经调用then方法注册的与异步任务关联的回调函数,以及then方法返回的promise对象。 理解这个代码的时候,不能把resolve和reject方法的细节带进来,而是从resolve和reject的作用去理解。他们的作用分别是为了resolve掉或reject掉deferred上面保存的那个promise对象。
小结
到这里为止,then/promise这个库该记录与分享的东西就差不多了,在源码部分的描述可能会让有的人觉得理解不了,这个确实是挺难的,每个人描述和理解的思路都不同,而且源码的解读本身就是一个比较纠结的事情。 所以学习它更好的办法是自己去研究、琢磨,等到自己领悟到了它的核心思路,那我这里怎么写怎么说都不重要了。
写这篇笔记只是为了记录、分享这样一个比较简单的Promise实现思想。