js基础:promise 的简单实现和需要注意的一些点

Promise 的一些注意点,以及梳理

前言

昨天面试去 xiaoman, 提到了 promise;
面对不断深入发问, 我脑子一片空白, 然后就想到了白云山;
我想怎么思考其他问题的时候没有这么思维活跃;

常用方法

1
2
3
4
5
6
7
8
9
10
Promise.prototype.then()
Promise.prototype.catch()
Promise.prototype.finally()
Promise.all() // 必须所有都 resolved
Promise.race() // 只要有一个 resolved
Promise.allSettled() // 所有都结束,不管是 resolved 还是 rejected
Promise.any() // 只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。 与 race 只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。
Promise.resolve()
Promise.reject()
Promise.try()

Promise 状态的不可逆性

Promise 状态的一旦变成 resolved 或 rejected 时,Promise 的状态和值就固定下来了,不论你后续再怎么调用 resolve 或 reject 方法,都不能改变它的状态和值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var p1 = new Promise(function (resolve, reject) {
resolve('success1')
resolve('success2')
})

var p2 = new Promise(function (resolve, reject) {
resolve('success')
reject('reject')
})

p1.then(function (value) {
console.log(value)
})

p2.then(function (value) {
console.log(value)
})
// "success1"
// "success"

resolve 或 reject 后面的代码会不会执行

当然会

then 的第二个参数和 catch

catch相当于then(null,function(err){ /*处理错误*/})的写法

1
2
3
4
5
6
7
8
9
10
11
12
// 写法一
new Promise((resolve) => {
foo.bar()
}).then(
(res) => {
console.log(res)
},
(err) => {
console.log(err)
}
)
// ReferenceError: foo is not defined
1
2
3
4
5
6
7
8
9
10
11
12
// 写法二
new Promise((resolve) => {
foo.bar()
})
.then((res) => {
console.log(res)
kk()
})
.catch((err) => {
console.log(err)
})
// ReferenceError: foo is not defined

上面代码中,catch要好于then(null,err=>{})写法, catch 可以捕获前面 then 方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch方法,而不使用then方法的第二个参数。如下:

1
2
3
4
5
6
7
8
9
10
11
new Promise((resolve) => {
resolve(1)
})
.then((res) => {
console.log(res)
kk()
})
.catch((err) => {
console.log(err)
})
// ReferenceError: kk is not defined

then/catch 的链式调用

首先, 在new Promise中需要执行resolve或者reject改变状态,否则不会执行then方法, 像下面then就不会执行:

1
2
3
4
5
6
7
8
9
10
var p = new Promise(function (resolve, reject) {
return 1
})
p.then(function (value) {
console.log(value)
return value * 2
}).then(function (value) {
console.log(value)
})
// Promise {<pending>}

在状态改变后, then方法返回一个新的Promise对象,因此可以通过链式调用then方法.
catch也可以链式调用, 因为catch相当于then(null,err=>{})

函数的返回值将被用作创建then返回的Promise对象, 有如下情况:

  • return 一个同步的值(当没有返回时,默认返回undefined),then方法将返回一个resolved状态的Promise对象,Promise对象的值就是这个返回值。
  • return 一个 Promisethen方法将根据这个Promise的状态和值创建一个新的Promise对象返回。
  • throw 一个同步异常,then方法将返回一个rejected状态的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
var p = new Promise(function (resolve, reject) {
resolve(1)
})
p.then(function (value) {
//第一个then
console.log(value)
return value * 2
})
.then(function (value) {
//第二个then
console.log(value)
})
.then(function (value) {
//第三个then
console.log(value)
return Promise.resolve('resolve')
})
.then(function (value) {
//第四个then
console.log(value)
return Promise.reject('reject')
})
.then(
function (value) {
//第五个then
console.log('resolve: ' + value)
},
function (err) {
console.log('reject: ' + err)
}
)
1
2
3
4
5
1
2
undefined
"resolve"
"reject: reject"

再写一个return返回promise的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var a = new Promise((resolve) => {
setTimeout(() => {
resolve('5s later')
}, 5000)
})
var p = new Promise(function (resolve, reject) {
resolve(1)
})
p.then(function (value) {
//第一个then
console.log(1)
return a
}).then(function (value) {
//第二个then
console.log(value)
})

// 1
// 5s later

Promise.resolve()

Promise.resolve()(注意,是Promise.resolve(),而不是new Promise中的resolve)可以接收一个值或者是一个Promise对象作为参数。
当参数是普通值时,它返回一个resolved状态的Promise对象;
当参数是Promise对象时,对象的值就是这个参数;
下面的例子可以看到 p1,p2 是相等的

1
2
3
4
let p1 = Promise.resolve(1)
let p2 = Promise.resolve(p1)
p1 === p2
// true

then 拿到数据发现不合适, 怎么触发 catch

throw new Error()抛出错误. 或者 Promise.reject()

resolve 和 reject

resolve的参数是一个Promise对象时,resolve会”拆箱”获取这个Promise对象的状态和值,但这个过程是异步的。
即最后的状态是根据传入的promise对象的状态确定的;
如下: 执行的是resolve(a), 但是a5 秒后返回的状态是rejected,所以会执行rejected回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var a = new Promise((resolve, reject) => {
setTimeout(() => {
reject('error')
}, 5000)
})
var p = new Promise(function (resolve, reject) {
var b = resolve(a)
console.log(a === b)
})
p.then(
function (value) {
console.log('success')
},
function (err) {
console.log(err)
return err
}
)
// false
// Promise {<pending>}
// error

但是, reject方法不同, 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
25
26
27
28
29
30
31
var a = new Promise((resolve, reject) => {
setTimeout(() => {
reject('error after 5s')
}, 5000)
})

var p = new Promise(function (resolve, reject) {
reject(a)
})

p.then(function (value) {
console.log('success')
})
.catch((err1) => {
// 第一个 catch
console.log('fail: ' + err1)
return err1
})
.catch((err2) => {
// 第二个 catch
console.log(err2)
return 1
})
.then((res) => {
console.log(res)
})

// fail: [object Promise]
// Promise {<pending>}
// error after 5s
// 1

可以看到, prejecta, 直接把a传到了第一个 catch, 第一个 catch 中, 打印出来参数, 就是promise对象a .
在 第一个catchreturn err1, 等待 5 秒后a变成rejected状态, 触发第二个 catch , 捕获到结果error after 5s,
最后catch 可以继续链式调用, 返回1, 相当于resolve(1), 最后then打印出1.

promise 中出现异常, promise 外的后面的代码会执行吗

会, Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。

1
2
3
4
5
6
new Promise(function (resolve, reject) {
resolve(x + 1)
})
console.log(333)
// 333
// Uncaught (in promise) ReferenceError: x is not defined

promise.all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let a = new Promise((resolve) => {
setTimeout(() => {
resolve(111)
}, 3000)
})
let b = new Promise((resolve) => {
setTimeout(() => {
resolve(222)
}, 1000)
})
let c = new Promise((resolve) => {
setTimeout(() => {
resolve(333)
}, 100)
})

Promise.all([b, a, c]).then((res) => {
console.log(res)
})
// 结果数组也是顺序的
// [222, 111, 333]
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
let a = new Promise((resolve) => {
setTimeout(() => {
console.log('haha')
resolve(111)
}, 3000)
})
let b = new Promise((resolve, reject) => {
setTimeout(() => {
reject(222)
}, 1000)
})
let c = new Promise((resolve) => {
setTimeout(() => {
resolve(333)
}, 100)
})

Promise.all([b, a, c])
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
})
只要有一个错误, 就会catch该错误
// 222
// haha

Promise 如何按顺序执行

有一个数组,数组元素是返回 Promise 对象的函数,怎样顺序执行数组中的函数,即在前一个数组中的 Promise resolve 之后才执行下一个函数?
….

  • 先生成数据, 下面代码执行完,生成 10 个函数组成的数组, 均返回 promise 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function createPromises(id) {
return function () {
return new Promise((resolve) => {
setTimeout(() => {
console.log(id)
resolve(id)
}, 1000)
})
}
}

let list = []

for (let i = 0; i < 10; i++) {
list.push(createPromises(i))
}
// (10) [ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ]
  • 递归法
1
2
3
4
5
6
7
8
9
// 递归调用
function promise_queue(list, i) {
if (i >= 0 && i < list.length) {
list[i]().then(() => {
promise_queue(list, i + 1)
})
}
}
promise_queue(list, 0)
  • await
1
2
3
4
5
6
7
8
async function promise_queue(list) {
let index = 0
while (index >= 0 && index < list.length) {
await list[index]()
index++
}
}
promise_queue(list)
  • Promise.resolve()
    初始状态是Promise.resolve(), then执行第一个, 并把下一个作为thensuccess函数(因为then返回一个promise的话, promise状态改变后, 才会执行下一个then)
1
2
3
4
5
6
7
function promise_queue(list) {
var sequence = Promise.resolve()
list.forEach((item) => {
sequence = sequence.then(item)
})
return sequence
}
  • reduce
1
list.reduce((pre, next) => pre.then(next), Promise.resolve())

promise 实现每隔一段时间执行一次函数, 执行 n 次 , 不用 await

每隔 interval 毫秒, 执行一次 fn 函数, 执行 time 次:

1
2
3
4
5
6
7
8
9
10
11
12
13
function interval(fn, interval, time) {
let f = () =>
new Promise((resolve) => {
setTimeout(() => {
fn()
resolve()
}, interval)
})
let a = Promise.resolve()
for (let i = 0; i < time; i++) {
a = a.then(f)
}
}

简易版本实现 promise

promise 特性:

  1. 状态不可逆,从 pending => fulfilled / rejected
  2. 有 then、catch 方法,还有 all、race、any 方法
  3. 同一个 promise 可以有多个 then
  4. then 可以链式调用
  5. 已经为 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
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
// 判断变量否为function
const isFunction = (variable) => typeof variable === 'function'
// 定义Promise的三种状态常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class MyPromise {
constructor(handle) {
if (!isFunction(handle)) {
throw new Error('MyPromise must accept a function as a parameter')
}
// 添加状态
this._status = PENDING
// 添加状态
this._value = undefined
// 添加成功回调函数队列
this._fulfilledQueues = []
// 添加失败回调函数队列
this._rejectedQueues = []
// 执行handle
try {
handle(this._resolve.bind(this), this._reject.bind(this))
} catch (err) {
this._reject(err)
}
}
// 添加resovle时执行的函数
_resolve(val) {
const run = () => {
if (this._status !== PENDING) return
// 依次执行成功队列中的函数,并清空队列
const runFulfilled = (value) => {
let cb
while ((cb = this._fulfilledQueues.shift())) {
cb(value)
}
}
// 依次执行失败队列中的函数,并清空队列
const runRejected = (error) => {
let cb
while ((cb = this._rejectedQueues.shift())) {
cb(error)
}
}
/* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
*/
if (val instanceof MyPromise) {
val.then(
(value) => {
this._value = value
this._status = FULFILLED
runFulfilled(value)
},
(err) => {
this._value = err
this._status = REJECTED
runRejected(err)
}
)
} else {
this._value = val
this._status = FULFILLED
runFulfilled(val)
}
}
// 为了支持同步的Promise,这里采用异步调用
setTimeout(run, 0)
}
// 添加reject时执行的函数
_reject(err) {
if (this._status !== PENDING) return
// 依次执行失败队列中的函数,并清空队列
const run = () => {
this._status = REJECTED
this._value = err
let cb
while ((cb = this._rejectedQueues.shift())) {
cb(err)
}
}
// 为了支持同步的Promise,这里采用异步调用
setTimeout(run, 0)
}
// 添加then方法
then(onFulfilled, onRejected) {
const { _value, _status } = this
// 返回一个新的Promise对象
return new MyPromise((onFulfilledNext, onRejectedNext) => {
// 封装一个成功时执行的函数
let fulfilled = (value) => {
try {
if (!isFunction(onFulfilled)) {
onFulfilledNext(value)
} else {
let res = onFulfilled(value)
if (res instanceof MyPromise) {
// 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
res.then(onFulfilledNext, onRejectedNext)
} else {
//否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
onFulfilledNext(res)
}
}
} catch (err) {
// 如果函数执行出错,新的Promise对象的状态为失败
onRejectedNext(err)
}
}
// 封装一个失败时执行的函数
let rejected = (error) => {
try {
if (!isFunction(onRejected)) {
onRejectedNext(error)
} else {
let res = onRejected(error)
if (res instanceof MyPromise) {
// 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
res.then(onFulfilledNext, onRejectedNext)
} else {
//否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
onFulfilledNext(res)
}
}
} catch (err) {
// 如果函数执行出错,新的Promise对象的状态为失败
onRejectedNext(err)
}
}
switch (_status) {
// 当状态为pending时,将then方法回调函数加入执行队列等待执行
case PENDING:
this._fulfilledQueues.push(fulfilled)
this._rejectedQueues.push(rejected)
break
// 当状态已经改变时,立即执行对应的回调函数
case FULFILLED:
fulfilled(_value)
break
case REJECTED:
rejected(_value)
break
}
})
}
// 添加catch方法
catch(onRejected) {
return this.then(undefined, onRejected)
}
// 添加静态resolve方法
static resolve(value) {
// 如果参数是MyPromise实例,直接返回这个实例
if (value instanceof MyPromise) return value
return new MyPromise((resolve) => resolve(value))
}
// 添加静态reject方法
static reject(value) {
return new MyPromise((resolve, reject) => reject(value))
}
// 添加静态all方法
static all(list) {
return new MyPromise((resolve, reject) => {
/**
* 返回值的集合
*/
let values = []
let count = 0
for (let [i, p] of list.entries()) {
// 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
this.resolve(p).then(
(res) => {
values[i] = res
count++
// 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
if (count === list.length) resolve(values)
},
(err) => {
// 有一个被rejected时返回的MyPromise状态就变成rejected
reject(err)
}
)
}
})
}
// 添加静态race方法
static race(list) {
return new MyPromise((resolve, reject) => {
for (let p of list) {
// 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
this.resolve(p).then(
(res) => {
resolve(res)
},
(err) => {
reject(err)
}
)
}
})
}
finally(cb) {
return this.then(
(value) => MyPromise.resolve(cb()).then(() => value),
(reason) =>
MyPromise.resolve(cb()).then(() => {
throw reason
})
)
}
}