promise实现-2:读别人的实现

看了好几篇别人的博客, 才把大概的原理搞清楚…
其实异步思想算是好理解, 但是代码现实也是够麻烦的

参考这两个实现:
https://github.com/xieranmaya/blog/issues/3
https://github.com/bruce-xu/Promise/blob/master/Promise.js

一.结构

构造函数

在 promise 对象中增加分别代表成功回调和失败回调的两个数组,数组中的每一项是通过内部封装的闭包函数调用的结果,也是一个函数。
不过这个函数可以访问到内部调用闭包时传递的 promise 对象,因此通过这种方式也可以访问到我们需要的下一个 promise 以及其关联的成功、失败回调的引用

1
2
3
4
5
6
7
8
9
function Promise(resolver) {
this._status = 'pending';//以此来标识当前 promise 对象的状态,并会触发后续 promise 的执行
this._doneCallbacks = [];//then方法push进来, 后续调用
this._failCallbacks = [];
resolver(resolve, reject);//任务成功与否由调用者控制,且需要在成功或失败时调用resolve或reject函数
...
}

resolve/reject

我们使用 promise,是期望在未来的某个时刻能获得一个结果,并且可用于接下来的 promise 调用。
所以resolve函数需要有一个参数来接收结果(同样,promise 执行失败后,我们也希望在后续 promise 中获得此失败信息,做相应处理。所以reject函数也需要有一个参数来接收错误)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function resolve(promise, data) {
if (promise._status !== 'pending') {
return;
}
promise._status = 'fullfilled';
promise._value = data;
run(promise);
}
function reject(promise, reason) {
if (promise._status !== 'pending') {
return;
}
promise._status = 'rejected';
promise._value = reason;
run(promise);
}

二.then方法

Promise 代表着一个承诺。作为承诺,总需要有一个结果,无论成功与否。如果成功,我们会获得需要的结果;当然也有可能会失败。
then方法接收两个参数:onResolve和onReject,分别代表当前 promise 对象在成功或失败时,接下来需要做的操作。
比如形如promise.then().then()…。为了方便链式调用,then方法的实现中,都会返回一个新的 promise 对象,在then方法中增加闭包调用以及为前一个 promise 对象保存引用

1
2
3
4
5
6
7
8
9
10
11
12
13
Promise.prototype.then = function(onResolve, onReject) {
//新的Promise
var promise = new Promise(function() {});
//保存新的Promise引用,处理方法,处理方式
this._doneCallbacks.push(makeCallback(promise, onResolve, 'resolve'));
this._failCallbacks.push(makeCallback(promise, onReject, 'reject'));
//如果在一个已经被fullfilled或rejected的promise上调用then,则需要直接执行通过then注册的回调函数
run(this);
return promise;//返回新的promise
}

此处then方法内创建的 promise 对象和暴露给用户直接调用的 Promise 构造函数所创建的 promise 对象有些不同。
用户调用 Promise 构造函数时需要传递resolver参数代表与此 promise 对象关联的任务,且任务会立即执行。

由此可知以下两点:
1.首先then方法中创建的 promise 关联的任务不能在 promise 对象创建时立即执行,所以先传入一个空函数以符合 Promise 构造函数调用格式;
2.其次前一个 promise 对象需要能够知道下一个 promise 对象是谁,其关联的任务是什么,这样才能在自己完成后调用下一个 promise 的任务。

因此前一个 promise 需要持有下一个 promise 以及其任务的引用。
由于 promise 的执行可能会成功也可能会失败,因此后一个 promise 一般会提供成功或失败后需要执行的任务供前一个 promise 调用。

三.回调

执行回调

resolve和reject函数中首先判断了当前 promise 的状态,如果不是pending(即已经被 resolve 或 reject 过了,不再重复执行)
然后赋予 promise 新的状态,并保存成功或失败的值。最后调用run函数。run函数用于触发接下来的 promise 的执行。run函数中需要注意的一点是,需要异步执行相关的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function run(promise) {
// `then`方法中也会调用,所以此处仍需做一次判断
if (promise._status === 'pending') {
return;
}
var value = promise._value;
var callbacks = promise._status === 'fullfilled'
? promise._doneCallbacks
: promise._failCallbacks;
// Promise异步操作的核心语句(先把then都追加完了, 然后依次调用队列里的callbacks)
setTimeout(function () {
for (var i = 0, len = callbacks.length; i < len; i++) {
callbacks[i](value);
}
});
// 每个promise只能被执行一次。虽然`_doneCallbacks`和`_failCallbacks`用户不应该直接访问,但还是可以访问到,保险起见,做清空处理。
promise._doneCallbacks = [];
promise._failCallbacks = [];
}

保存之前状态的callback

由于成功回调onResolve和失败回调onReject都通过此闭包封装,所以在闭包中增加了第三个参数action,以区分是哪种回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
function makeCallback(promise, callback, action) {
return function promiseCallback(value) {
// 如果传递了callback,则使用前一个promise传递过来的值作为参数调用callback,
// 并根据callback的调用结果来处理当前promise
if (typeof callback === 'function') {
var x;
try {
x = callback(value);
} catch (e) {
// 如果调用callback时抛出异常,则直接用此异常对象reject当前promise
reject(promise, e);
}
// 如果callback的返回值是当前promise,为避免造成死循环,需要抛出异常
// 根据Promise+规范,此处应抛出TypeError异常
if (x === promise) {
var reason = new TypeError('TypeError: The return value could not be same with the promise');
reject(promise, reason);
}
// 如果返回值是一个Promise对象,则当返回的Promise对象被resolve/reject后,再resolve/reject当前Promise
else if (x instanceof Promise) {
x.then(
function (data) {
resolve(promise, data);
},
function (reason) {
reject(promise, reason);
}
);
} else {
var then;
(function resolveThenable(x) {
// 如果返回的是一个Thenable对象(此处逻辑有点坑,参照Promise+的规范实现)
if (x && (typeof x === 'object'|| typeof x === 'function')) {
try {
then = x.then;
} catch (e) {
reject(promise, e);
return;
}
if (typeof then === 'function') {
// 调用Thenable对象的`then`方法时,传递进去的`resolvePromise`和`rejectPromise`方法(及下面的两个匿名方法)
// 可能会被重复调用。但Promise+规范规定这两个方法有且只能有其中的一个被调用一次,多次调用将被忽略。
// 此处通过`invoked`来处理重复调用
var invoked = false;
try {
then.call(
x,
function (y) {
if (invoked) {
return;
}
invoked = true;
// 避免死循环
if (y === x) {
throw new TypeError('TypeError: The return value could not be same with the previous thenable object');
}
// y仍有可能是thenable对象,递归调用
resolveThenable(y);
},
function (e) {
if (invoked) {
return;
}
invoked = true;
reject(promise, e);
}
);
} catch (e) {
// 如果`resolvePromise`和`rejectPromise`方法被调用后,再抛出异常,则忽略异常
// 否则用异常对象reject此Promise对象
if (!invoked) {
reject(promise, e);
}
}
}
else {
resolve(promise, x);
}
}
else {
resolve(promise, x);
}
}(x));
}
}
// 如果未传递callback,直接用前一个promise传递过来的值resolve/reject当前Promise对象
else {
action === 'resolve'
? resolve(promise, value)
: reject(promise, value);
}
};
}

四.done

done(或者fail或catch)就是then的语法糖

1
2
3
done: function (onResolve) {
return this.then(onResolve, null);
},

五.小结

小结一下:
Promise的主要思想就是用闭包把异步的操作保存下来, 然后通过多个Promise串联 依次调用(所以每个then必须返回一个新的Promise对象)

大概流程就是:
Promise构造函数(初始化状态等) -> resolve方法(改状态) -> run方法(setTimeout(0)加入js队列) -> 后续then()循环… -> js队列开始依次执行setTimeout(0)的回调
至此一个then完事了,但是这个then里如果有新的Promise对象, 就又续上了. 所以就是 重复 或者完成了

巧妙也是麻烦之处就是一个Promise对象, 能完成上述的循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
new Promise(function(resolve,reject){
console.log("==>1构造函数")
resolve("构造函数传参");
}).then(function(data){
console.log("==>2第一个then");
return new Promise(function(resolve,reject){
setTimeout(function(){
console.log("==>3延迟_"+data);
resolve();
}, 2000);
});
}).done(function(data){
console.log("==>4第二个then_"+data);
})