字节前端实习一面就让我手写 Promise,没写出直接挂了 💔,亡羊补牢,为时不晚。

使用方法回顾

作为 ES6 提出来的新语法,首先回顾一下 Promise 的使用方法。

1
2
3
4
5
6
7
const promise = new Promise((reslove, reject) => {
reslove(233)
})

promise.then((value) => {
console.log(value) // 233
})

简单来说就是实例化一个 Promise 对象,实例化的时候传一个函数进去,这个函数有两个变量:

  • resolve,是一个回调函数,用于处理成功的数据
  • reject,也是一个回调函数,用于处理失败的信息

简易版 Promise

简易版 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
const PENDING = 'pending'
const FULLFILLED = 'resolved'
const REJECTED = 'rejected'

class MyPromise {
constructor(executor) {
// 实例化的时候立刻执行传入的这个参数
executor(this.resolve, this.reject)
}

// 初始化状态为 pending
status = PENDING

// value 存储成功的结果,reason 存储失败的结果
value = null
reason = null

resolve = (value) => {
// 只有当状态为 pending 的时候才执行成功回调
if (this.status === PENDING) {
this.status = FULLFILLED
this.value = value
}
}

reject = (reason) => {
// 只有当状态为 pending 的时候才执行失败回调
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
}
}

then(onFullfilled, onRejected) {
if (this.status === FULLFILLED) {
// 如果传入的实例的状态是成功,则执行成功回调
onFullfilled(this.value)
} else if (this.status === REJECTED) {
// 如果传入的实例的状态是失败,则执行失败回调
onRejected(this.reason)
}
}
}

测试成功:

1
2
3
4
5
6
7
const promise = new MyPromise((reslove, rejectct) => {
reslove(233)
})

promise.then((value) => {
console.log(value) // 233
})

然而这样子的代码是无法实现异步操作的,因为我们的 then 方法是在主线程中直接运行的,不会受到 executor(也就是新建 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
// 存储成功回调函数
onFulfilledCallback = null
// 存储失败回调函数
onRejectedCallback = null

resolve = (value) => {
// 只有当状态为 pending 的时候才执行成功回调
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
// 如果存在成功回调,就调用
this.onFulfilledCallback && this.onFulfilledCallback(value)
}
}

reject = (reason) => {
// 只有当状态为 pending 的时候才执行失败回调
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
// 如果存在成失败回调,就调用
this.onRejectedCallback && this.onRejectedCallback(reason)
}
}

then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
} else if (this.status === REJECTED) {
onRejected(this.reason)
} else if (this.status === PENDING) {
// 如果传入的实例仍是等待中,那么需要先存储成功和失败的回调函数
this.onFulfilledCallback = onFulfilled
this.onRejectedCallback = onRejected
}
}

但是这个方式有个很明显的缺陷,就是我们的 promise 一旦有多个 then,它的原先的回调函数就会被新的回调函数覆盖掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})

promise.then((value) => {
console.log(1)
console.log(value)
})

promise.then((value) => {
console.log(2)
console.log(value)
})

promise.then((value) => {
console.log(3)
console.log(value)
})

// 3
// success

很容易就能想到,用数组来存储所有的成功回调和失败回调函数即可。

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
// 存储成功回调函数
onFulfilledCallback = []
// 存储失败回调函数
onRejectedCallback = []

resolve = (value) => {
// 只有当状态为 pending 的时候才执行成功回调
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
// 如果存在成功回调,就调用
while (this.onFulfilledCallback.length) {
// 这里用 shift() 保证了每个成功回调只会运行一次
this.onFulfilledCallback.shift()(value)
}
}
}

reject = (reason) => {
// 只有当状态为 pending 的时候才执行失败回调
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
// 如果存在成失败回调,就调用
while (this.onRejectedCallback.length) {
this.onRejectedCallback.shift()(value)
}
}
}

then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
// 如果传入的实例的状态是成功,则执行成功回调
onFulfilled(this.value)
} else if (this.status === REJECTED) {
// 如果传入的实例的状态是失败,则执行失败回调
onRejected(this.reason)
} else if (this.status === PENDING) {
// 如果传入的实例仍是等待中,那么需要先存储成功和失败的回调函数
this.onFulfilledCallback.push(onFulfilled)
this.onRejectedCallback.pop(onRejected)
}
}

链式调用

我们一鼓作气,再把 then 的链式调用给实现了。

逻辑也很清晰,就是在调用 then 的时候,我们需要判断一下,执行成功回调函数的返回值是否是一个 MyPromise 对象,两种情况:

  • MyPromise 对象,就继续调用返回的这个对象的 then 方法
  • 是普通值,就直接调用成功的回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
then(onFulfilled, onRejected) {
// 为了实现链式调用,需要返回一个 MyPromise 对象
return new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 获取成功回调的结果
const ret = onFulfilled(this.value)
if (ret instanceof MyPromise) {
// 如果返回值仍然是一个 MyPromise 实例,则进行链式调用
ret.then(resolve, reject)
} else {
// ret 是普通值,则直接调用成功回调
resolve(ret)
}
} else if (this.status === REJECTED) {
// 如果传入的实例的状态是失败,则执行失败回调
onRejected(this.reason)
} else if (this.status === PENDING) {
// 如果传入的实例仍是等待中,那么需要先存储成功和失败的回调函数
this.onFulfilledCallback.push(onFulfilled)
this.onRejectedCallback.pop(onRejected)
}
})
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
promise
.then((value) => {
console.log(1)
console.log(value)
return new MyPromise((resolve) => resolve('success'))
})
.then((value) => {
console.log(2)
console.log(value)
return new MyPromise((resolve) => resolve('success'))
})
.then((value) => {
console.log(3)
console.log(value)
})
// 1
// success
// 2
// success
// 3
// success

但是,这个实现还有很多限制,我们逐步解决。

循环调用

按我们上面的写法,我们如果在 promise.then 里面返回了同一个 promise,结果会报 Uncaught ReferenceError 的错误。

1
2
3
4
5
6
7
8
const promise = new MyPromise((resolve, reject) => {
resolve('success')
})

const p = promise.then((value) => {
console.log(value)
return p
})

image-20210423113756496

但是,官方的 Promise 报的却是 Type Error 的错误。

1
2
3
4
5
6
7
8
const promise = new Promise((resolve, reject) => {
resolve('success')
})

const p = promise.then((value) => {
console.log(value)
return p
})

image-20210423113849223

该怎么还原呢,我们尝试修改 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
then(onFulfilled, onRejected) {
// 为了实现链式调用,需要返回一个 MyPromise 对象
const p = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 创建一个微任务
queueMicrotask(() => {
// 获取成功回调的结果
try {
const ret = onFulfilled(this.value)
if (p === ret) {
// 如果返回值和原 promise 相同,抛出循环调用异常
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')
)
}
if (ret instanceof MyPromise) {
// 如果返回值仍然是一个 MyPromise 实例,则进行链式调用
ret.then(resolve, reject)
} else {
// ret 是普通值,则直接调用成功回调
resolve(ret)
}
} catch (error) {
reject(error)
}
})
} else if (this.status === REJECTED) {
// 如果传入的实例的状态是失败,则执行失败回调
onRejected(this.reason)
} else if (this.status === PENDING) {
// 如果传入的实例仍是等待中,那么需要先存储成功和失败的回调函数
this.onFulfilledCallback.push(onFulfilled)
this.onRejectedCallback.pop(onRejected)
}
})

return p
}

在这里,还记得之前为了实现链式调用而新建了 MyPromise 对象,当时是直接 return 了这个对象,现在我们对其命名为 p,并且保存下来,以便后续判断循环调用。

只需要在获取成功回调返回值的时候,判断其是否和我们新建的 p 相同

由于 pNew 完之后才能获得的一个对象,所以直接在原本的 then 函数中增加判重逻辑即可。

验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const promise = new MyPromise((resolve, reject) => {
resolve('new success')
})

// 定义一个循环调用的 promise
const p = promise.then((value) => {
console.log(1)
console.log(value)
return p
})

p.then(
(value) => {
console.log(2)
console.log(value)
},
(reason) => {
console.log(3)
console.log(reason)
}
)

image-20210423192328052

捕获错误

在 Promise 中,为了避免发生错误就中断代码运行,我们需要对两个地方的错误进行捕获。

  1. 在实例化 MyPromise 的时候

  2. then 的过程中

针对实例化的错误肯定是在构造函数中进行捕捉,实际上只需要对 executor 执行的增加异常捕获操作。

1
2
3
4
5
6
7
8
9
constructor(executor) {
try {
// 实例化的时候立刻执行传入的这个参数
executor(this.resolve, this.reject)
} catch (error) {
// 捕获错误并执行失败回调
this.reject(error)
}
}

针对 then 方法中出现的错误,显然就是处理对其进行异常捕获啦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
queueMicrotask(() => {
// 获取成功回调的结果
try {
const ret = onFulfilled(this.value)
if (p === ret) {
// 如果返回值和原 promise 相同,抛出循环调用异常
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')
)
}
if (ret instanceof MyPromise) {
// 如果返回值仍然是一个 MyPromise 实例,则进行链式调用
ret.then(resolve, reject)
} else {
// ret 是普通值,则直接调用成功回调
resolve(ret)
}
} catch (error) {
reject(error)
}
})
...

让我们来看看效果是否和我们预期一样。

构造函数中捕获错误:

1
2
3
4
const promise = new MyPromise((resolve, reject) => {
throw new Error('new failed')
resolve('success')
})

image-20210423191321826

then 中捕获错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const promise = new MyPromise((resolve, reject) => {
resolve('new success')
})

promise
.then(
(value) => {
console.log(1, value)
throw new Error('then failed')
},
(reason) => {
console.log(2, reason)
}
)
.then(
(value) => {
console.log(3, value)
},
(reason) => {
console.log(4, reason)
}
)

image-20210423192718969

优化 then 中的 REJECTED 和 PENDING 状态

先前我们对 then 方法中 FULFLLED 状态的操作进行了优化,包括:

  • 创建微任务,实现检测循环调用错误
  • 执行回调时增加捕获异常功能

现在我们也对 REJECTEDPENDING 状态进行以上补充。

首先为了书写方便,先定义一个处理 promise 返回值的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function resolvePromise(p, ret, resolve, reject) {
if (p === ret) {
// 如果返回值和原 promise 相同,抛出循环调用异常
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')
)
}
if (ret instanceof MyPromise) {
// 如果返回值仍然是一个 MyPromise 实例,则进行链式调用
ret.then(resolve, reject)
} else {
// ret 是普通值,则直接调用成功回调
resolve(ret)
}
}

修改 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
51
then(onFulfilled, onRejected) {
// 为了实现链式调用,需要返回一个 MyPromise 对象
const p = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 创建一个微任务
queueMicrotask(() => {
// 获取成功回调的结果
try {
const ret = onFulfilled(this.value)
resolvePromise(p, ret, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if (this.status === REJECTED) {
// 如果传入的实例的状态是失败,则执行失败回调
queueMicrotask(() => {
try {
const ret = onRejected(this.reason)
resolvePromise(p, ret, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if (this.status === PENDING) {
// 如果传入的实例仍是等待中,那么需要先存储成功和失败的回调函数
this.onFulfilledCallback.push(() => {
queueMicrotask(() => {
try {
const ret = onFulfilled(this.value)
resolvePromise(p, ret, resolve, reject)
} catch (error) {
reject(error)
}
})
})
this.onRejectedCallback.push(() => {
queueMicrotask(() => {
try {
const ret = onRejected(this.value)
resolvePromise(p, ret, resolve, reject)
} catch (error) {
reject(error)
}
})
})
}
})

return p
}

then 方法中的默认函数

在调用 then 方法的时候,应该允许不写成功和失败的回调。

1
2
3
4
5
6
7
8
9
10
11
12
then(onFulfilled, onRejected) {
// 未指定成功和失败回调时,使用默认函数
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (value) => value
onRejected =
typeof onRejected === 'function'
? onRejected
: (reason) => {
throw reason
}
...
}

resolve 和 reject 的静态调用

原生的 Promise 是支持无需实例化,直接调用 Promise.resolve() 来返回一个 Promise 对象的。

如果我们尝试用自定义的 MyPromise 进行这种操作:

1
2
3
4
5
6
7
8
MyPromise.resolve()
.then(() => {
console.log('1')
return MyPromise.resolve('success')
})
.then((value) => {
console.log(2, value)
})

报错提示如下:

image-20210423200556984

因为是需要调用类方法,所以我们需要用 static 关键字声明 resolvereject 的静态方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyPromise {
...
// resolve 静态方法
static resolve(parameter) {
if (parameter instanceof MyPromise) {
return parameter
}
return new MyPromise((resolve) => {
resolve(parameter)
})
}

// reject 静态方法
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
}

测试结果:

image-20210423201541575

代码优化

对声明类时的冗余代码进行优化,优化完代码如下:

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
const PENDING = 'pending'
const FULFILLED = 'resolved'
const REJECTED = 'rejected'

class MyPromise {
constructor(executor) {
try {
// 实例化的时候立刻执行传入的这个执行器,并传入 resolve 和 reject
executor(this.resolve, this.reject)
} catch (error) {
// 捕获错误并执行失败回调
this.reject(error)
}
}

// 初始化状态为 pending
status = PENDING

// value 存储成功的结果,reason 存储失败的结果
value = null
reason = null

// 存储成功回调函数
onFulfilledCallback = []
// 存储失败回调函数
onRejectedCallback = []

resolve = (value) => {
// 只有当状态为 pending 的时候才执行成功回调
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
// 如果存在成功回调,就调用
while (this.onFulfilledCallback.length) {
this.onFulfilledCallback.shift()(value)
}
}
}

reject = (reason) => {
// 只有当状态为 pending 的时候才执行失败回调
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
// 如果存在成失败回调,就调用
while (this.onRejectedCallback.length) {
this.onRejectedCallback.shift()(reason)
}
}
}

then(onFulfilled, onRejected) {
// 未指定成功和失败回调时,使用默认函数
const realOnFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (value) => value
const realOnRejected =
typeof onRejected === 'function'
? onRejected
: (reason) => {
throw reason
}
// 为了实现链式调用,需要返回一个 MyPromise 对象
const p = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 创建一个微任务
queueMicrotask(() => {
// 获取成功回调的结果
try {
const x = realOnFulfilled(this.value)
resolvePromise(p, x, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if (this.status === REJECTED) {
// 如果传入的实例的状态是失败,则执行失败回调
queueMicrotask(() => {
try {
const x = (realo = OnRejected(this.reason))
resolvePromise(p, x, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if (this.status === PENDING) {
// 如果传入的实例仍是等待中,那么需要先存储成功和失败的回调函数
this.onFulfilledCallback.push(() => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(p, x, resolve, reject)
} catch (error) {
reject(error)
}
})
})
this.onRejectedCallback.push(() => {
queueMicrotask(() => {
try {
const ret = onRejected(this.value)
resolvePromise(p, ret, resolve, reject)
} catch (error) {
reject(error)
}
})
})
}
})

return p
}

// resolve 静态方法
static resolve(parameter) {
if (parameter instanceof MyPromise) {
return parameter
}
return new MyPromise((resolve) => {
resolve(parameter)
})
}

// reject 静态方法
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
}

function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
// 如果返回值和原 promise 相同,抛出循环调用异常
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')
)
}
if (x instanceof MyPromise) {
// 如果返回值仍然是一个 MyPromise 实例,则进行链式调用
x.then(resolve, reject)
} else {
// x 是普通值,则直接调用成功回调
resolve(x)
}
}

面向 Promise A+ 规范

为了更加符合 Promise A+ 的要求,需要对 resolvePromise 进行优化。

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
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
// 如果返回值和原 promise 相同,抛出循环调用异常
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')
)
}
if (typeof x === 'object' || typeof x === 'function') {
if (x === null) {
return resolve(x)
}

let then
try {
then = x.then
} catch (error) {
return reject(error)
}

if (typeof then === 'function') {
let called = false
try {
then.call(
x,
(y) => {
if (called) return
called = true
resolvePromise(promise, y, resolve, reject)
},
(r) => {
if (called) return
called = true
reject(r)
}
)
} catch (error) {
if (called) return
reject(error)
}
} else {
resolve(x)
}
} else {
// x 是普通值,则直接调用成功回调
resolve(x)
}
}

参考资料

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

https://juejin.cn/post/6945319439772434469#comment