简单实现 Promise

Promise是前端异步回调的一个解决方案,更多的介绍其实网上都有。但是Promise规范有很多,如Promise/A,Promise/B,Promise/D 以及 Promise/A 的升级版Promise/A+。

0x00 前言

在阅读规范后,现在简单按照 Promise/A+ 的规范手撸一个简单的Promise对象。主要是为了加深认识Promise的规范和理解使用。

【翻译】Promises/A+规范

0x01 条件

首先我们需要了解一个简单的Promise至少需要满足什么条件。

  1. Promise 的实例是具有状态的,而状态包括:等待态(Pending)、执行态(Fulfilled)、拒绝态(Rejected)。
  2. 一个 Promise 必须提供一个 then 方法以访问其当前值、终值和据因。

0x02 实现

02.1 定义

首先我们通过一个立即执行匿名函数构建一个封闭的作用域,避免污染问题。

使用Promise的时候,我们都需要去创建一个新的实例,如:new Promise(...),所以我们需要最后返回的是一个Function

简单延伸一下,new做了什么操作:在Javascript中new是作为一个保留的关键词存在,其中new的操作是隐式的新建一个临时的空白对象,并拷贝了function.prototype的属性,最后将构造函数中的this指向这个临时新建的对象,最后返回这个临时对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var PromiseA = (() => {
const PENDING = 0; //等待态
const FULFILLED = 1; //执行态
const REJECTED = 2; //拒绝态

function PromiseA(fn) {
this.state = PENDING;
this.handlers = [];
fn(this._resolve.bind(this), this._reject.bind(this));
}

Object.assign(PromiseA.prototype,{...}) // 后续逐个解释

return PromiseA;
})();

用于现在大部分浏览器都已经默认支持Promise对象,为了避免冲突所以另起PromiseA作为例子。

构造函数中默认赋值 this.state = PENDING,当实例新建的时候实例即处于等待状态。

由于规范中要求:then 方法可以被同一个 promise 调用多次。所以 this.handlers = [],用于在实例还处于PENDING状态时将多个 then 方法收集起来。

fn(this.resolve.bind(this), this.reject.bind(this)) 立即调用创建实例时传入的函数方法,并且将 resolve\reject 方法绑定当前 promise 实例后以参数形式传递。

到这里Promise的构造函数就写完了,接下来我们需要根据规范完成其他都应的方法。

02.2 promise.then

  • promise 的 then 方法接受两个参数:promise.then(onFulfilled, onRejected)
  • then 方法必须返回一个 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
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
/**
* promise 的 then 方法
**/
PromiseA.prototype.then(onFulfilled, onRejected) {
return new PromiseA((resolve, reject) => {
this._registerHandler(
result => {
try {
if (onFulfilled && onFulfilled instanceof Function) {
resolve(onFulfilled(result));
} else {
resolve(result);
}
} catch (err) {
reject(err);
}
},
reason => {
try {
if (onRejected && onRejected instanceof Function) {
resolve(onRejected(reason));
} else {
reject(reason);
}
} catch (err) {
reject(err);
}
}
);
});
}

/**
* 注册then的回调方法,如果是PENDING状态则添加到队列。
* 如果是FULFILLED 、 REJECTED 则直接执行方法
**/
PromiseA.prototype._registerHandler(onFulfilled, onRejected) {
if (this.state === PENDING) {
this.handlers.push({
onFulfilled,
onRejected
});
} else {
setTimeout(() => {
if (this.state === FULFILLED) {
onFulfilled(this.result);
} else {
onRejected(this.reason);
}
}, 0);
}
}

根据上面的两个条件,我们知道每个then方法的返回值是 promise 对象,所以直接创建一个新的 Promise。

this._registerHandler 这段代码中由于使用了箭头函数,所以 this 指向的是调用了 then 方法的 promise 实例,而非新创建的返回实例。this._registerHandler 将根据当前的 promise 实例状态判断执行不断的操作,如果当前状态是PENDING则将回调方法暂时存放在 handlers 中,如果是FULFILLED或者REJECTED则直接执行其中对应的回调。

this._registerHandler接受的第一个参数是成功后的回调方法,第二个则是失败的。

如果 onFulfilled 不是函数,其必须被忽略
如果 onRejected 不是函数,其必须被忽略

根据规则,如果判断这两个参数不是函数,则直接跳过处理。但有个地方需要注意的是,经过promise.then#onRejected的方法处理后如果没有抛出新的错误或者返回一个新的Promise.reject,接下来的状态则应该是FULFILLED

如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)

最后要说的是为什么setTimtou(,0),因为无论是静态值还是回调返回的值,当执行resolve后,then的回调方法都会放在微任务(mircoTask)队列中等待执行。所以这里简单用setTimeout模拟了。

02.3 _resolve _reject

经过上面的代码热身大家应该能有一点感觉了,但其最核心主要的还是构造函数中的fn(this._resolve.bind(this), this._reject.bind(this));这段代码。

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
PromiseA.prototype._resolve(result) {
let then = result && this._getThen(result);
if (then) {
try {
then.call(
result,
thenResult => {
if (this.state !== PENDING) return;
this._fulfill(thenResult);
},
thenReason => {
if (this.state !== PENDING) return;
this._reject(thenReason);
}
);
} catch (e) {
if (this.state !== PENDING) return;
this._reject(e);
debugger;
console.error(e);
}
} else {
this._fulfill(result);
}
}

PromiseA.prototype._getThen(result) {
let then = result.then;
if (then && then instanceof Function) {
return then;
}
return null;
}

PromiseA.prototype._fulfill(result) {
if (this.state !== PENDING) return;
this.state = FULFILLED;
this.result = result; // 必须拥有一个不可变的终值

// 调用回调函数
this.handlers.forEach(handler => {
handler.onFulfilled(result);
});
}

PromiseA.prototype._reject(reason) {
if (this.state !== PENDING) return;
this.state = REJECTED;
this.reason = reason; //必须拥有一个不可变的据因

this.handlers.forEach(handler => {
handler.onRejected(reason);
});
}

首先我们看下规则:

x 为 Promise
如果 x 为 Promise ,则使 promise 接受 x 的状态 注4:
如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
如果 x 处于执行态,用相同的值执行 promise
如果 x 处于拒绝态,用相同的据因拒绝 promise

x指的是代码中的result

从规则中看到,我们需要在_resolve函数中判断返回值是否 Promise 对象,如果是的话需要等待这个promise直至被执行或者拒绝,这个时候我们需要对这个 promise 注册回调,当回调成功的时候则调用this._fulfill。如果不是 Promise 则直接调用 this._fulfill

再看 _getThen,其实只是满足其中规则中定义的thenable(是一个定义了 then 方法的对象或函数。)去获取then方法。

_fulfill_reject。判断当前实例状态是否PENDING,因为根据状态中的规则来看只有PENDING可以迁移至执行态或拒绝态,并且不可逆。

0x03 图解示例

  1. 创建一个新的Promise实例(promise1)时,创建的参数是一个函数,函数接收两个参数,分别是函数:resolve、reject。通过调用其中一个函数决定当前Promise实例处于何种状态。
  2. 当Promise实例调用then的时候,会立即创建一个新的Promise实例(thenPromise1)并返回。创建新的Promise时候,会在调用then的promise实例(promise1)中注册新的handler。
  3. 注册handler的时候,会判断promise实例(promise1)是否处于PENDING状态,如果是的话则将方法放入队列,否则直接执行对应状态的方法。

promise

0x04 完整代码

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
var PromiseA = (global => {
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;
let id = 0;
function PromiseA(fn) {
this.id = ++id;
this.state = PENDING;
this.handlers = [];
fn.call(global, this._resolve.bind(this), this._reject.bind(this));
}

Object.assign(PromiseA.prototype, {
then(onFulfilled, onRejected) {
return new PromiseA((resolve, reject) => {
this._registerHandler(
result => {
try {
if (onFulfilled && onFulfilled instanceof Function) {
resolve(onFulfilled(result));
} else {
resolve(result);
}
} catch (err) {
reject(err);
}
},
reason => {
try {
if (onRejected && onRejected instanceof Function) {
resolve(onRejected(reason));
} else {
reject(reason);
}
} catch (err) {
reject(err);
}
}
);
});
},
_resolve: function(result) {
let then = result && this._getThen(result);
if (then) {
try {
then.call(
result,
thenResult => {
if (this.state !== PENDING) return;
this._fulfill(thenResult);
},
thenReason => {
if (this.state !== PENDING) return;
this._reject(thenReason);
}
);
} catch (e) {
if (this.state !== PENDING) return;
this._reject(e);
debugger;
console.error(e);
}
} else {
this._fulfill(result);
}
},
_reject(reason) {
this._reject(reason);
},
_registerHandler(onFulfilled, onRejected) {
if (this.state === PENDING) {
this.handlers.push({
onFulfilled,
onRejected
});
} else {
setTimeout(() => {
if (this.state === FULFILLED) {
onFulfilled(this.result);
} else {
onRejected(this.reason);
}
}, 0);
}
},
_fulfill(result) {
if (this.state !== PENDING) return;
this.state = FULFILLED;
this.result = result;
this.handlers.forEach(handler => {
debugger;
handler.onFulfilled(result);
});
},
_reject(reason) {
if (this.state !== PENDING) return;
this.state = REJECTED;
this.reason = reason;
this.handlers.forEach(handler => {
handler.onRejected(reason);
});
},
_getThen(result) {
let then = result.then;
if (then && then instanceof Function) {
return then;
}
return null;
}
});

return PromiseA;
})(this);

0x05 最后

PromiseA.resolve 的实现其实也很简单,各类扩展的方法就不展开讨论。最后简单的说一下PromiseA.resolve的函数怎么实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var PromiseA = (() => {

function PromiseA(fn) {
this.state = PENDING;
this.handlers = [];
fn(this._resolve.bind(this), this._reject.bind(this));
}

//...

PromiseA.resolve = function(result){
return new PromiseA(resolve=>resolve(result));
}

return PromiseA;
})();