这次我们要根据 Promise 的规范 实现一个简单的 Promise。

思路

我们从实际场景出发,从简单的应用中反推 Promise 的实现,然后根据逐步拓展的应用场景和规范,不断改进我们的 Promise。

最简单的实现
首先我们先设想一个最简单的应用场景:

1
2
var p = MyPromise((resolve) => setTimeout(() => resolve('finish!', 3000));
p.then((message) => console.log(message));

从使用场景中我们可以看出首先 Promise 的构造函数需要接收一个函数,而这个函数的参数是 resolve,然后 Promise 需要一个 then 函数来告诉 Promise 当操作结束时做什么,参照规范中的 2.2 我们这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyPromise {
constructor(fn){
// 规则 2.2.2.2 中要求 onFulFilled 无法在 promise 结束前调用,这就要求该变量是私有的,由于 es6 模拟私有变量过于麻烦,因此用下划线表示变量私有
this._onFulFilled = () => {};
this.resolve = this.resolve.bind(this);

fn(this.resolve);
}

resolve(message) {
this._onFulFilled(message);
}

then(onFulFilled){
this._onFulFilled = onFulFilled && onFulFilled instanceof Function ? onFulFilled : this._onFulFilled;
}
}

好的,现在我们初步的 Promise 完成了,但是我们发现一个问题,如果 then 里面我们要求多个参数那怎么办?如:

1
2
var p = new MyPromise((resolve) => setTimeout(() => resolve('ok', 'now', 'we', 'finish'), 3000));
p.then((message1, message2, message3, message4) => console.log(message1 + message2 + message3 + message4));

我们要利用 apply 来修改一下 onFulFilled 函数:

1
2
3
resolve(message) {
this._onFulFilled.apply(this, arguments);
}

我们要考虑到失败的情况,使用场景代码如下:

1
2
3
4
5
6
7
8
var p = new MyPromise((resolve, reject) => {
setTimeout(() => {
let isExpection = Math.random() > 0.5;
if (isExpection) reject('error!');
else resolve('finish!')
}, 3000);
});
p.then((message) => console.log(message), (message) => console.log(message));

对应我们加上 reject 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MyPromise {
constructor(fn) {
this._onFulFilled = () => { };
this._onRejected = () => { };

this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);

fn(this.resolve, this.reject);
}

resolve() {
this._onFulFilled.apply(this, arguments);
}

reject() {
this._onRejected.apply(this, arguments);
}

then(onFulFilled, onRejected) {
this._onFulFilled = onFulFilled && onFulFilled instanceof Function ? onFulFilled : this._onFulFilled;
this._onRejected = onRejected && onRejected instanceof Function ? onRejected : this._onRejected;
}
}

实现状态判断

现在的 Promise 只能执行一次,当我们第二次执行 then 后不会返回结果,使用场景如下:

1
2
3
var p = new MyPromise((resolve) => setTimeout(() => resolve('finish'), 1000));
p.then((message) => console.log(message));
p.then((message) => console.log(message));

首先我们要把 onFulFilled 改为一个数组,然后让 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
class MyPromise {
constructor(fn) {
this._resolve_data = null;
this._error_data = null;

this._onFulFilleds = [];
this._onRejecteds = [];

this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
this.then = this.then.bind(this);

// 1: pending
// 2: fulfilled
// -1: rejected
this.state = 1;
fn(this.resolve, this.reject);
}

resolve() {
this._resolve_data = arguments;
this._onFulFilleds.forEach(resolve => {
resolve.apply(this, arguments);
});
this.state = 2;
}

reject() {
this._error_data = arguments;
this._onRejecteds.forEach(reject => reject.apply(this, arguments));
this.state = -1;
}

then(onFulFilled, onRejected) {
if (onFulFilled && onFulFilled instanceof Function) {
this._onFulFilleds.push(onFulFilled);
}
if (onRejected && onRejected instanceof Function) {
this._onRejecteds.push(onRejected);
}

switch(this.state){
case 2:
onFulFilled.apply(this, this._resolve_data);
break;
case -1:
onRejected.apply(this, this._error_data);
break;
}
return this;
}
}

实现链式调用

为了 Promise 用得更加美观,我们的 Promise 需要实现链式调用,其使用场景如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function nextPromise(data) {
return new MyPromise((resolve) => setTimeout(() => resolve(data), 1000));
}

nextPromise('data')
.then((message) => {
console.log(message + '1');
})
.then((message) => {
console.log(message + '2');
})
.then((message) => {
console.log(message + '3');
})
.then((message) => {
console.log(message + '4');
})
.then((message) => {
console.log(message + '5');
});

我们只需要在 then 中返回 this 就行,实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
then(onFulFilled, onRejected) {
if (onFulFilled && onFulFilled instanceof Function) {
this._onFulFilleds.push(onFulFilled);
}
if (onRejected && onRejected instanceof Function) {
this._onRejecteds.push(onRejected);
}
switch(this.state){
case 2:
onFulFilled.apply(this, this._resolve_data);
break;
case -1:
onRejected.apply(this, this._error_data);
break;
}
return this;
}

实现 Promise 间的嵌套

我们使用 Promise 的一大目的是它帮我们解决了回调地狱的问题,目前我们没有处理当 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
function nextPromise(data) {
return new MyPromise((resolve) => setTimeout(() => resolve(data), 1000));
}

nextPromise('1')
.then((message) => {
console.log(message);
return nextPromise('2')
})
.then((message) => {
console.log(message);
return nextPromise('3')
})
.then((message) => {
console.log(message);
return nextPromise('4')
})
.then((message) => {
console.log(message);
return nextPromise('5')
})
.then((message) => {
console.log(message);
});

这里我主要参考这篇文章的做法,我们在 resolve 和 reject 函数进行处理,我们先判断 then 返回的是不是 Promise,是的话我们把剩余的 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
reject() {
this._error_data = arguments;
this.state = -1;
for (let i = 0; i < this._onRejecteds.length; ++i) {
let result = this._onRejecteds[i].apply(this, arguments);
if (result instanceof MyPromise) {
for (i++; i < this._onRejecteds.length; ++i) {
let onFulFilled = this._onFulFilleds.length > i ? this._onFulFilleds[i] : () => { };
let onRejecteds = this._onRejecteds.length > i ? this._onRejecteds[i] : () => { };
result.then(onFulFilled, onRejecteds);
}
}
}
}

then(onFulFilled, onRejected) {
if (onFulFilled && onFulFilled instanceof Function) {
this._onFulFilleds.push(onFulFilled);
}
if (onRejected && onRejected instanceof Function) {
this._onRejecteds.push(onRejected);
}

switch (this.state) {
case 2:
onFulFilled.apply(this, this._resolve_data);
break;
case -1:
onRejected.apply(this, this._error_data);
break;
}
return this;
}

总结

目前这样一个简易的 Promise 到此完成,从中我们也会发现其实写一个玩具轮子不难,难的是让这个轮子在工业中可以实际应用,源代码放在:github。