0%

手撕 Promise(then 链及 Promise 方法实现)

Promise 类基础逻辑

Promise 可以解决的问题

  1. 解决回调地狱问题,不会导致难以维护
  2. 合并多个异步请求,节约时间

基础逻辑

  1. new Promise:Promise 是一个类,在执行这个类的时候,需要传递一个执行器进去,执行器会立即执行

  2. Promise 中有三种状态,分别为:成功(fulfilled)、失败(rejected)、等待(pending),一旦状态确定就不可更改

    如果状态不是等待,需要阻止程序向下执行

  3. resolve 和 reject 函数是用来更改状态的

    把 resolve 和 reject 定义为箭头函数是为了让函数内部的 this 指向,指向类的实例对象(Promise)

  4. then 方法内部做的事情就是判断状态,如果状态是成功,就调用成功的回调函数,如果状态是失败,就调用失败的回调函数。then 方法定义在原型对象上的

  5. 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
30
31
32
33
34
35
36
37
38
39
40
41
// 可复用且有提示
const PENDING = 'pending' // 等待
const FULFILLED = 'fulfilled' // 成功
const REJECTED = 'rejected' // 失败
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
// promise 状态
status = PENDING
// 成功之后的值
value = undefined
// 失败之后的原因
reason = undefined
resolve = value => {
// 如果状态不是等待,阻止程序向下执行
if (this.status !== PENDING) return
// 将状态更改为成功
this.status = FULFILLED
// 保存成功之后的值
this.value = value
}
reject = reason => {
// 如果状态不是等待,阻止程序向下执行
if (this.status !== PENDING) return
// 将状态更改为失败
this.status = REJECTED
// 保存失败之后的原因
this.reason = reason
}
then(successCallback, failCallback) {
// 判断状态
if (this.status === FULFILLED) {
successCallback(this.value)
} else if (this.status === REJECTED) {
failCallback(this.reason)
}
}
}

module.exports = MyPromise

之后对其进行验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const MyPromise = require('./myPromise')

let promise = new MyPromise((resolve, reject) => {
resolve('成功')
reject('失败')
})
promise.then(
value => {
console.log(value)
},
reason => {
console.log(reason)
}
)

加入 then 方法和异步逻辑

问题1: 如果要给 resolve 或 reject 包裹一层 setTimeout,发现什么都没有输出

  • 主线程是不会等待异步 setTimeout 执行完成的,then 会立即执行
  • 由于当前 Promise 执行到 then 时状态为 等待态,而现在只判断成功与失败的状态,所以会什么都不输出
1
2
3
4
5
6
7
8
let promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('成功')
}, 2000)
})
promise.then(value => {
console.log(value) // 没有输出
})

问题2: 当 then 方法被多次调用时,每一个 then 方法中传递的回调函数都是要执行的

  • 同步:如果调用 then 方法时,已经知道 promise 状态为 成功态或失败态,就可以直接调用回调即可
  • 异步:如果调用 then 方法时,promise 状态为 等待态,每一个 then 方法的回调函数都应该存储起来,当状态为成功或失败时,再依次调用回调函数
1
2
3
4
5
6
7
8
9
let promise = new MyPromise((resolve, reject) => {
resolve('成功')
})
promise.then(value => {
console.log(value) // 成功
})
promise.then(value => {
console.log(value) // 没有输出
})

接下来处理上面出现的两个问题

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
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
status = PENDING
value = undefined
reason = undefined
// 成功回调 undefined -> []
successCallback = []
// 失败回调 undefined -> []
failCallback = []
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
// 判断成功回调是否存在,如果存在调用
// this.successCallback && this.successCallback(this.value)
while (this.successCallback.length) this.successCallback.shift()(this.value)
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
// 判断失败回调是否存在,如果存在调用
// this.failCallback && this.failCallback(this.value)
while (this.failCallback.length) this.failCallback.shift()(this.reason)
}
then(successCallback, failCallback) {
if (this.status === FULFILLED) {
successCallback(this.value)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
// 将成功回调和失败回调存储起来
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
}
}

module.exports = MyPromise

实现 then 链

链式调用

then 方法是可以被链式调用的,后面 then 方法的回调函数拿到上一个 then 方法的回调函数的返回值

1
2
3
4
5
6
7
8
9
let promise = new MyPromise((resolve, reject) => {
resolve('成功')
})
promise.then(value => {
console.log(value) // 成功
return 100
}).then(value => {
console.log(value) // 没有输出
})

接下来需要实现如下需求

  • 实现 then 方法链式调用(then 方法返回一个 promise)

  • 如何把上一个 then 回调函数的返回值传递给下一个 then 方法的回调函数

    这里需要判断回调函数返回值是普通值还是 Promise 对象

    • 如果是普通值,直接调用 resolve
    • 如果是 promise 对象,查看 promise 对象返回的结果,根据结果决定调用 resolve 还是 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
25
26
27
28
29
30
31
32
class MyPromise {
then(successCallback, failCallback) {
// 返回一个新的 Promise
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
let x = successCallback(this.value)
// 判断 x 的是普通值还是 promise 对象
resolvePromise(x, resolve, reject)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
})
return promise2
}
}

function resolvePromise(x, resolve, reject) {
if (x instanceof MyPromise) {
// promise 对象
/* x.then(
value => resolve(value),
reason => reject(reason)
) */
x.then(resolve, reject)
} else {
// 普通值
resolve(x)
}
}

循环引用

如果在 p1 里返回 p1 这个 Promise,就会发生循环调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let promise = new MyPromise((resolve, reject) => {
resolve('成功')
})
let p1 = promise.then(value => {
console.log(value)
return p1
})

p1.then(
value => {
console.log(value)
},
reason => {
console.log(reason) // TypeError: Chaining cycle detected for promise #<Promise>
}
)

注意: 举个例子 var obj = { n: 10, x: obj.n *10 },因为 obj 还没有创建完,而在创建属性 x 时是获取不到 obj.n 的。因为全局作用于只声明了 obj,却没有赋值(obj -> undefiend),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 MyPromise {
then(successCallback, failCallback) {
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 变为异步任务这样 promise2 就赋值完成了
setTimeout(() => {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
}, 0)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
})
return promise2
}
}

function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}

捕获错误

  1. 当执行器(executor)中代码执行时发生错误,将 promise 状态改为失败态

  2. 回调函数在执行时发生错误,这个错误要在下个 then 方法的回调函数中捕获到

    当代码为失败状态或等待态也需要用 try...catch 包裹

  3. 当代码为等待态时,如果碰到异步,不能把回调函数直接 push 到数组里,这样没有办法对其进行处理,我们可以 push 一个函数进去,函数里面调用成功或失败回调

    这时就可以对进行异步和错误捕获处理了

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
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
// 捕获执行器错误
try {
executor(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
status = PENDING
value = undefined
reason = undefined
successCallback = []
failCallback = []
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
while (this.successCallback.length) this.successCallback.shift()()
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
while (this.failCallback.length) this.failCallback.shift()()
}
then(successCallback, failCallback) {
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
// 捕获回调函数发生的错误
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = failCallback(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else {
// push 函数进去,函数里面调用成功回调函数
this.successCallback.push(() => {
setTimeout(() => {
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
// push 函数进去,函数里面调用失败回调函数
this.failCallback.push(() => {
setTimeout(() => {
try {
let x = failCallback(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
})
return promise2
}
}

function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}

module.exports = MyPromise

then 方法参数变为可选

链式调用 then 时,即使不传递参数也会依次向后传递

1
2
3
4
5
6
7
const promise = new MyPromise((resolve, reject) => {
resolve(100)
})
promise
.then()
.then()
.then(value => console.log(value)) // 100

其实就是判断 successCallbackfailCallback 是否存在,如果不存在给它进行赋值

1
2
3
4
5
6
7
8
9
10
11
class MyPromise {
then(successCallback, failCallback) {
successCallback = successCallback ? successCallback : value => value
failCallback = failCallback
? failCallback
: reason => {
throw reason
}
}
}

其他方法实现

Promise.all

  • all 方法是通过类直接调用,所以 all 方法是一个静态方法
  • all 传入的必须是一个数组,如果数组中某一项不是 Promise 实例,需要把它转换为成功的 Promise 实例
  • 之后每一项依次执行,只要有一项失败返回就是失败的,必须全部成功才是成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function p1() {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('p1')
}, 2000)
})
}
function p2() {
return new MyPromise((resolve, reject) => {
resolve('p2')
})
}

MyPromise.all(['a', 'b', p1(), p2(), 'c']).then(result => console.log(result)) // [ 'a', 'b', 'p1', 'p2', 'c' ]

注意:

  • 返回结果的顺序跟传入的顺序一致(不能使用 push,因为不能确定谁先到,需要使用索引)
  • for 循环执行就是一瞬间的,但是里面可能存在异步操作,需要等待所有都执行完,再执行 resolve 操作
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
class MyPromise {
static all(array) {
let result = []
let index = 0
return new MyPromise((resolve, reject) => {
function addData(key, value) {
result[key] = value
index++
if (index === array.length) {
resolve(result)
}
}
for (let i = 0; i < array.length; i++) {
const current = array[i]
if (current instanceof MyPromise) {
// promise 对象
current.then(
value => addData(i, value),
reason => reject(reason)
)
} else {
// 普通值
addData(i, array[i])
}
}
})
}
}

Promise.resolve

1
2
3
4
5
6
7
function p1() {
return new MyPromise((resolve, reject) => {
resolve('p1')
})
}
Promise.resolve(10).then(value => console.log(value))
Promise.resolve(p1()).then(value => console.log(value))
  • 如果参数是 Promise 实例,那么 Promise.resolve 将不做任何修改,原封不动地返回这个实例
  • 有如果参数是普通值,就直接将值转换为成功的 Promise 实例
1
2
3
4
5
6
class MyPromise {
static resolve(value) {
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
}

finally

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
function p1() {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('p1')
}, 2000)
})
}
function p2() {
return new MyPromise((resolve, reject) => {
resolve('p2')
})
}

p2()
.finally(() => {
console.log('finally')
return p1()
})
.then(
value => {
console.log(value)
},
reason => {
console.log(reason)
}
)
  • 无论当前 Promise 最终状态是成功的还是失败的,finally 中的回调函数始终都会被执行一次
  • 在 finally 方法后面链式调用 then 方法拿到当前这个 promise 最终返回的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyPromise {
finally(callback) {
return this.then(
value => {
return MyPromise.resolve(callback()).then(() => value)
},
reason => {
return MyPromise.resolve(callback()).then(() => {
throw reason()
})
}
)
}
}

catch

  • 处理当前 promise 为失败状态,内部调用的也是 then 方法(只注册失败回调)
1
2
3
4
5
class MyPromise {
catch(failCallback) {
return this.then(undefined, failCallback)
}
}

完整版

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
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
try {
executor(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
status = PENDING
value = undefined
reason = undefined
successCallback = []
failCallback = []
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
while (this.successCallback.length) this.successCallback.shift()()
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
while (this.failCallback.length) this.failCallback.shift()()
}
then(successCallback, failCallback) {
successCallback = successCallback ? successCallback : value => value
failCallback = failCallback
? failCallback
: reason => {
throw reason
}
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = failCallback(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else {
this.successCallback.push(() => {
setTimeout(() => {
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.failCallback.push(() => {
setTimeout(() => {
try {
let x = failCallback(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
})
return promise2
}
finally(callback) {
return this.then(
value => {
return MyPromise.resolve(callback()).then(() => value)
},
reason => {
return MyPromise.resolve(callback()).then(() => {
throw reason()
})
}
)
}
catch(failCallback) {
return this.then(undefined, failCallback)
}
static all(array) {
let result = []
let index = 0
return new MyPromise((resolve, reject) => {
function addData(key, value) {
result[key] = value
index++
if (index === array.length) {
resolve(result)
}
}
for (let i = 0; i < array.length; i++) {
const current = array[i]
if (current instanceof MyPromise) {
current.then(
value => addData(i, value),
reason => reject(reason)
)
} else {
addData(i, array[i])
}
}
})
}
static resolve(value) {
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
}

function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}

module.exports = MyPromise