先来看下Promise的常见应用:

image.png

观察者模式

观察上面的这个例子,我们来分析Promise的调用流程:

  • Promise的构造方法接受一个executor函数,在new Promise的时候这个executor立即执行
  • executor内部的异步任务被放入微任务队列等待执行
  • then被执行,收集成功/失败回调,放入成功/失败队列
  • executor的异步任务被执行,触发resolve/reject,从成功/失败队列中取出回调依次执行

由上面的分析得知这是一种典型的观察者模式。这是典型的“收集依赖->触发依赖->取出依赖执行”的方式,被广泛运用于观察者模式 ,在Promise中执行顺序是“then收集依赖->异步触发resolve->resolve执行依赖”。由此我们可以勾勒出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
class Promise{
constructor(executor){
this.resolveQueue = []; // then收集执行成功的回调队列
this.rejectQueue = []; // then收集执行失败的回调队列

let resolve = val => {
while (this.resolveQueue.length) {
const callback = this.resolveQueue.shift();
callback(val);
}
};
let reject = val => {
while (this.rejectQueue.length) {
const callback = this.rejectQueue.shift();
callback(val);
}
};
executor(resolve,reject);
}

/**
* 接受一个成功的回调和一个失败的回调并push进对应的队列
* @param resolveFn
* @param rejectFn
*/
then(resolveFn,rejectFn){
this.resolveQueue.push(resolveFn);
this.rejectQueue.push(rejectFn);
}
}

Promise A+规范

以上,我们用观察者模式简单实现了能够在then方法的回调中取得异步操作的返回值,下面我们使用Promise A+规范来补充下这个Promise。

Promise 本质是一个状态机,且状态只能为以下三种:Pending(等待态)、Fulfilled(执行态)、Rejected(拒绝态),状态的变更是单向的,只能从 Pending -> Fulfilled 或 Pending -> Rejected,状态变更不可逆
then方法接收两个可选参数,分别对应状态改变时触发的回调。then 方法返回一个 promise。then 方法可以被同一个 promise 调用多次

image.png

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
class Promise{
constructor(executor){
this.status = PENDING;
this.resolveQueue = []; // then收集执行成功的回调队列
this.rejectQueue = []; // then收集执行失败的回调队列

let resolve = val => {

if (this.status !== PENDING) return; // 状态只能由pending转为fulfilled
this.status = FULFILLED; // 状态变更

while (this.resolveQueue.length) {
const callback = this.resolveQueue.shift();
callback(val);
}
};
let reject = val => {
if (this.status !== PENDING) return;
this.status = REJECTED;

while (this.rejectQueue.length) {
const callback = this.rejectQueue.shift();
callback(val);
}
};
executor(resolve,reject);
}

/**
* 接受一个成功的回调和一个失败的回调并push进对应的队列
* @param resolveFn
* @param rejectFn
*/
then(resolveFn,rejectFn){
this.resolveQueue.push(resolveFn);
this.rejectQueue.push(rejectFn);
}
}

then的链式调用

先来看个简单的例子:

image.png

思考下如何实现这种链式调用:

  1. promise可以不断地then下去,说明then方法本身会返回一个Promise,那些直接return 一个值的也会被包装为Promise
  2. then的回调需要按顺序执行。以上面的代码为例,虽然中间return了一个Promise,但执行的顺序仍然要保证是1-2-3,我们需要等待Promise的状态变更后再执行下一个then收集的回调

对then方法进行如下改写:

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
then(resolveFn, rejectFn) {

return new Promise((resolve, reject) => {

// 把resolveFn重新包装一下在放入 resolve 队列 ,这是为了能够获取then中的返回值进行分类讨论
const fulfilledFn = value => {
try {
// 执行当前的Promise的成功回调,并获取返回值
const x = resolveFn(value);
// 分类讨论返回值 ,如果是Promise,那么等待Promise的状态变更,否则直接resolve
x instanceof Promise ? x.then(resolve, reject) : resolve(x);
} catch (err) {
reject(err);
}
};
// 把后续then收集的依赖都放入当前Promise的 回调队列中,这是为了保证顺序调用
this.resolveQueue.push(fulfilledFn);

const rejectedFn = error => {
try {
const x = rejectFn(error);
x instanceof Promise ? x.then(resolve, reject) : resolve(x);
} catch (err) {
reject(err);
}
};
this.rejectQueue.push(rejectedFn);
});
}

值穿透 & 状态已变更情况

值穿透:根据规范,如果then接受的函数不是function,那么我们应该忽略它。如果没有忽略,当then回调不为function的时候会抛出异常,导致链式调用中断。
处理状态为resolve/reject的情况:其实我们上边 then() 的写法是对应状态为pendding的情况,但是有些时候,resolve/reject 在 then() 之前就被执行(比如Promise.resolve().then()),如果这个时候还把 then()回调 push 进 resolve/reject 的执行队列里,那么回调将不会被执行,因此对于状态已经变为fulfilled或rejected的情况,我们直接执行 then 回调

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
then(resolveFn, rejectFn) {

return new Promise((resolve, reject) => {

// 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
if (typeof resolveFn !== 'function') {
resolveFn = value => value;
}
if (typeof rejectFn !== 'function') {
rejectFn = reason => {
throw new Error(reason instanceof Error ? reason.message : reason);
};
}

// 把resolveFn重新包装一下在放入 resolve 队列 ,这是为了能够获取then中的返回值进行分类讨论
const fulfilledFn = value => {
try {
// 执行当前的Promise的成功回调,并获取返回值
const x = resolveFn(value);
// 分类讨论返回值 ,如果是Promise,那么等待Promise的状态变更,否则直接resolve
x instanceof Promise ? x.then(resolve, reject) : resolve(x);
} catch (err) {
reject(err);
}
};
const rejectedFn = error => {
try {
const x = rejectFn(error);
x instanceof Promise ? x.then(resolve, reject) : resolve(x);
} catch (err) {
reject(err);
}
};

switch (this.status) {
// 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
case PENDING:
this.resolveQueue.push(fulfilledFn);
this.rejectQueue.push(rejectedFn);
break;
// 当状态已经变为resolve/reject时,直接执行then回调
case FULFILLED:
fulfilledFn(this.value); // this.value是上一个then回调return的值(见完整代码)
break;
case REJECTED:
rejectedFn(this.value);
break;
}
});
}

兼容同步任务

完成了 then 的链式调用以后,我们再处理一个前边的细节,然后放出完整代码。上文我们说过,Promise 的执行顺序是 new Promise -> then()回调收集 -> resolve/reject执行回调。这一顺序是建立在executor是异步任务的前提上的。如果executor是一个同步任务,那么执行顺序会变成new Promise -> resolve/reject的回调 -> then收集回调(即resolve的执行跑到then前面去了,从而导致我们无法在then中得到Promise的resolve的值)。

image.png

为了兼容上述情况,我们给resolve/reject执行回调的操作包装一个setTimeout让其异步执行:

这里插一句,有关这个 setTimeout,其实还有一番学问。虽然规范没有要求回调应该被放进宏任务队列还是微任务队列,但其实 Promise 的默认实现是放进了微任务队列,我们的实现(包括大多数 Promise 手动实现和 polyfill 的转化)都是使用 setTimeout 放入了宏任务队列(当然我们也可以用 MutationObserver 模拟微任务)

代码如下:

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
//Promise/A+规定的三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class Promise {
// 构造方法接收一个回调
constructor(executor) {
this._status = PENDING; // Promise状态
this._value = undefined;// 储存then回调return的值
this._resolveQueue = []; // 成功队列, resolve时触发
this._rejectQueue = []; // 失败队列, reject时触发

// 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
let _resolve = val => {
//把resolve执行回调的操作封装成一个函数,放进setTimeout里,以兼容executor是同步代码的情况
const run = () => {
if (this._status !== PENDING) return;// 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = FULFILLED; // 变更状态
this._value = val; // 储存当前value

// 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
// 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
while (this._resolveQueue.length) {
const callback = this._resolveQueue.shift();
callback(val);
}
};
setTimeout(run);
};
// 实现同resolve
let _reject = val => {
const run = () => {
if (this._status !== PENDING) return;// 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = REJECTED; // 变更状态
this._value = val; // 储存当前value
while (this._rejectQueue.length) {
const callback = this._rejectQueue.shift();
callback(val);
}
};
setTimeout(run);
};
// new Promise()时立即执行executor,并传入resolve和reject
executor(_resolve, _reject);
}

// then方法,接收一个成功的回调和一个失败的回调
then(resolveFn, rejectFn) {
// 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
typeof resolveFn !== 'function' ? resolveFn = value => value : null;
typeof rejectFn !== 'function' ? rejectFn = reason => {
thrownewError(reason instanceof Error ? reason.message : reason);
} : null;

// return一个新的promise
return new Promise((resolve, reject) => {
// 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
const fulfilledFn = value => {
try {
// 执行第一个(当前的)Promise的成功回调,并获取返回值
let x = resolveFn(value);
// 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
x instanceof Promise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
};

// reject同理
const rejectedFn = error => {
try {
let x = rejectFn(error);
x instanceof Promise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
};

switch (this._status) {
// 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
case PENDING:
this._resolveQueue.push(fulfilledFn);
this._rejectQueue.push(rejectedFn);
break;
// 当状态已经变为resolve/reject时,直接执行then回调
case FULFILLED:
fulfilledFn(this._value); // this._value是上一个then回调return的值(见完整版代码)
break;
case REJECTED:
rejectedFn(this._value);
break;
}
})
}
}

测试:

image.png

Promise中的其他方法

以上我们就实现了Promise的主要功能,剩下的几个方法都非常简单:

Promise.prototype.catch,返回一个 Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected)相同:

1
2
3
4
// catch方法其实就是执行一下then的第二个回调
catch (rejectFn) {
return this.then(null, rejectFn);
}

Promise.prototype.finally()返回一个 Promise。在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。在 finally 之后,还可以继续 then。并且会将值原封不动的传递给后面的then。

1
2
3
4
5
6
finally(callback) {
return this.then(
value => new Promise(resolve => resolve(callback())).then(() => value),
reason => new Promise(resolve => resolve(callback()).then(() => throw reason))
);
}

Promise.resolve()返回一个以给定值解析后的 Promise 对象。如果该值为 promise,返回这个 promise;如果这个值是 thenable(即带有”then” 方法)),返回的 promise 会“跟随”这个 thenable 的对象,采用它的最终状态;否则返回的 promise 将以此值完成。此函数将类 promise 对象的多层嵌套展平。

1
2
3
4
static resolve(value) {
// 根据规范, 如果参数是Promise实例, 直接return这个实例
return value instanceof Promise ? value : new Promise(resolve => resolve(value));
}

Promise.reject()方法返回一个带有拒绝原因的 Promise 对象。

1
2
3
static reject(reason) {
return new Promise((resolve, reject) => reject(reason));
}

Promise.all(iterable)方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 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
static all(promiseArr) {
let index = 0;
let results = [];
return new Promise((resolve, reject) => {
for (let i = 0; i < promiseArr.length; i++) {
// Promise.resolve(p)用于处理传入值不为Promise的情况
Promise.resolve(promiseArr[i]).then(
value => {
index++;
results[i] = value;

// 所有then执行后, resolve结果
if (index === promiseArr.length) {
resolve(results);
}
},
err => {
// 有一个Promise被reject时,Promise的状态变为reject
reject(err);
}
)
}
});
}

Promise.race(iterable)方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。

1
2
3
4
5
6
7
8
9
10
11
static race(promiseArr) {
// 同时执行Promise,如果有一个Promise的状态发生改变,就变更新Promise的状态
return new Promise((resolve, reject) => {
for (const p of promiseArr) {
Promise.resolve(p).then(
value => resolve(value),
err => reject(err)
)
}
})
}

完整代码

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//Promise/A+规定的三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class Promise {
// 构造方法接收一个回调
constructor(executor) {
this._status = PENDING; // Promise状态
this._value = undefined;// 储存then回调return的值
this._resolveQueue = []; // 成功队列, resolve时触发
this._rejectQueue = []; // 失败队列, reject时触发

// 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
let _resolve = val => {
//把resolve执行回调的操作封装成一个函数,放进setTimeout里,以兼容executor是同步代码的情况
const run = () => {
if (this._status !== PENDING) return;// 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = FULFILLED; // 变更状态
this._value = val; // 储存当前value

// 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
// 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
while (this._resolveQueue.length) {
const callback = this._resolveQueue.shift();
callback(val);
}
};
setTimeout(run);
};
// 实现同resolve
let _reject = val => {
const run = () => {
if (this._status !== PENDING) return;// 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = REJECTED; // 变更状态
this._value = val; // 储存当前value
while (this._rejectQueue.length) {
const callback = this._rejectQueue.shift();
callback(val);
}
};
setTimeout(run);
};
// new Promise()时立即执行executor,并传入resolve和reject
executor(_resolve, _reject);
}

// then方法,接收一个成功的回调和一个失败的回调
then(resolveFn, rejectFn) {
// 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
typeof resolveFn !== 'function' ? resolveFn = value => value : null;
typeof rejectFn !== 'function' ? rejectFn = reason => {
thrownewError(reason instanceof Error ? reason.message : reason);
} : null;

// return一个新的promise
return new Promise((resolve, reject) => {
// 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
const fulfilledFn = value => {
try {
// 执行第一个(当前的)Promise的成功回调,并获取返回值
let x = resolveFn(value);
// 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
x instanceof Promise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
};

// reject同理
const rejectedFn = error => {
try {
let x = rejectFn(error);
x instanceof Promise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
};

switch (this._status) {
// 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
case PENDING:
this._resolveQueue.push(fulfilledFn);
this._rejectQueue.push(rejectedFn);
break;
// 当状态已经变为resolve/reject时,直接执行then回调
case FULFILLED:
fulfilledFn(this._value); // this._value是上一个then回调return的值(见完整版代码)
break;
case REJECTED:
rejectedFn(this._value);
break;
}
})
}

// catch方法其实就是执行一下then的第二个回调
catch(rejectFn) {
return this.then(null, rejectFn);
}

finally(callback) {
return this.then(
value => new Promise(resolve => resolve(callback())).then(() => value),
reason => new Promise(resolve => resolve(callback()).then(() => throw reason))
);
}

static resolve(value) {
// 根据规范, 如果参数是Promise实例, 直接return这个实例
return value instanceof Promise ? value : new Promise(resolve => resolve(value));
}

static reject(reason) {
return new Promise((resolve, reject) => reject(reason));
}

static all(promiseArr) {
let index = 0;
let results = [];
return new Promise((resolve, reject) => {
for (let i = 0; i < promiseArr.length; i++) {
// Promise.resolve(p)用于处理传入值不为Promise的情况
Promise.resolve(promiseArr[i]).then(
value => {
index++;
results[i] = value;

// 所有then执行后, resolve结果
if (index === promiseArr.length) {
resolve(results);
}
},
err => {
// 有一个Promise被reject时,Promise的状态变为reject
reject(err);
}
)
}
});
}

static race(promiseArr) {
// 同时执行Promise,如果有一个Promise的状态发生改变,就变更新Promise的状态
return new Promise((resolve, reject) => {
for (const p of promiseArr) {
Promise.resolve(p).then(
value => resolve(value),
err => reject(err)
)
}
})
}
}