目录
- 前言
- Promise核心原理实现
- Promise的使用分析
- MyPromise的实现
- 在Promise中加入异步操作
- 实现then方法的多次调用
- 实现then的链式调用
- then方法链式调用识别Promise对象自返回
- 捕获错误及 then 链式调用其他状态代码补充
- 捕获执行器错误
- 捕获then中的报错
- 错误与异步状态的链式调用
- 将then方法的参数变成可选参数
- Promise.all方法的实现
- Promise.resolve方法的实现
- finally方法的实现
- catch方法的实现
- 完整代码
前言
手写Promise现在已经成了面试的热门内容,但在实际开发中基本都不会去手写一个Promise,但是在面试中各种手写题可能就会遇到一个手写Promise,我们可以尽量提高我们的上限,从而获取更多的工作机会。
Promise核心原理实现
首先我们从使用的角度来分析一下Promise,然后编写一个最简单版本的Promise。
Promise的使用分析
Promise就是一个类,在执行这个类的时候,需要传递一个执行器(回调函数)进去,执行器会立即执行。
Promise中的状态分为三个,分别是:
- pending→等待
- fulfilled→成功
- rejected→失败
状态的切换只有两种,分别是:
- pending→fulfilled
- pending→rejected
一旦状态发生改变,就不会再次改变:
- 执行器中的两个参数,分别是resolve和reject,其实就是两个回调函数,调用resolve是从pending状态到fulfilled,调用reject是从状态pending到rejected。传递给这两个回调函数的参数会作为成功或失败的值。
- Promise的实例对象具有一个then方法,该方法接受两个回调函数,分别来处理成功与失败的状态,then方法内部会进行判断,然后根据当前状态确定调用的回调函数。then方法应该是被定义在原型对象中的。
- then的回调函数中都包含一个值,如果是成功,表示成功后返回的值;如果是失败就表示失败的原因。
MyPromise的实现
根据我们上面的分析,写出如下代码:
?
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 |
|
现在我们就来写一段代码验证一下上面的代码
验证resolve:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
验证reject:
?
1 2 3 4 5 6 7 8 9 10 11 12 |
|
验证状态不可变:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
在Promise中加入异步操作
如果我们的代码中存在异步操作,我们自己写的Promise将毫无用处,
例如下面这段代码:
?
1 2 3 4 5 6 7 8 9 |
|
这段代码2000ms后没有任何输出,为了解决这个问题我们将对自己编写的类进行如下操作:
- 创建两个实例方法用于存储我们传入的成功与失败的处理逻辑。
- 为
then
方法添加状态为pending
时的处理逻辑,这时将传递进来的属性保存到实例上。 - 在成功或者失败时调用相应的传递进来的回调函数(实例属性存在函数的情况下)。
实现代码如下:
?
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 |
|
这里的this.onFulfilled && this.onFulfilled(this.value)
表示如果该属性存在就调用这个方法。
现在我们重新执行一开始上面那一段代码,2s后会成功输出成功
。
实现then方法的多次调用
Promise实例中存在要给then
方法,允许我们在Promise实例中链式调用,每个then
方法还会返回一个Promise实例,
如下图所示:
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 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 |
|
这里我们通过数组的shift()
方法,该方法删除数组的第一个元素,并返回,返回的这个值正好是一个回调函数,然后调用该函数即可实现该功能。
实现then的链式调用
想要实现then的链式调用,主要解决两个问题:
- 返回的是一个新的
MyPormise
实例。 then
的返回值作为下一次的链式调用的参数。
这里分为两种情况:
- 直接返回一个值,可以直接作为值使用
- 返回一个新的
MyPormise
实例,此时就需要判断其状态
实现代码如下:
?
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 |
|
then方法链式调用识别Promise对象自返回
在Promise中,如果then
方法返回的是自己的promise
对象,则会发生promise
的嵌套,这个时候程序会报错,
测试代码如下:
?
1 2 3 4 5 6 7 8 9 |
|
想要解决这个问题其实我们只需要在then
方法返回的MyPromise实例对象与then中回调函数返回的值进行比对,如果相同的返回一个reject的MyPromise实例对象,并创建一个TypeError类型的Error。
实现代码如下:
?
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 |
|
这里then方法中的setTimeout
的作用并不是延迟执行,而是为了调用resolvePromise
函数时,保证创建的promise
存在。
捕获错误及 then 链式调用其他状态代码补充
到目前为止我们现实的Promise并没有对异常做任何处理,为了保证代码的健壮性,我们需要对异常做一些处理。
捕获执行器错误
现在我们需要对执行器进行异常捕获,如果发生异常,就将我们的状态修改为rejected
。
捕获执行器的错误也比较简单,只需要在构造函数中加入try...catch
语句就可以,
实现代码如下:
?
1 2 3 4 5 6 7 8 9 |
|
测试代码如下:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
捕获then中的报错
现在我们需要对then中的异常捕获到,并在下一次链式调用中传递到then的第二个函数中,实现的方式也是通过try...catch
语句,
示例代码如下:
?
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 |
|
测试代码如下:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
错误与异步状态的链式调用
现在只对成功状态的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 |
|
测试代码如下:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
将then方法的参数变成可选参数
Promise中的then方法其实是两个可以可选参数,如果我们不传递任何参数的话,里面的结果是向下传递的,直到捕获为止,
例如下面这段代码:
?
1 2 3 4 5 6 7 8 |
|
这段代码可以理解为:
?
1 2 3 4 5 6 7 |
|
所以说我们只需要在没有传递回调函数时,赋值一个默认的回调函数即可。
实现代码如下:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Promise.all方法的实现
关于all()
方法的使用,可以参数Promise.all()
。简单的说Promise.all()
会将多个Promise实例包装为一个Promise实例,且顺序与调用顺序一致,
示例代码如下:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
在这段代码中,我们的p1
的执行是延迟了2s的,这里如果不使用Promise.all()
的话最终顺序是与我们调用不同的。
现在我们来分析一下all()的实现思路:
all()
方法可以通过类直接调用,所以是一个静态方法all()
方法接收一个数组,数组中的值可以是一个普通值,也可以是一个MyPromise的实例对象- return一个新的MyPromise实例对象
- 遍历数组中的每一个值,判断值得类型,如果是一个普通值得话直接将值存入一个数组;如果是一个MyPromise的实例对象的话,会调用then方法,然后根据执行后的状态,如果失败的话调用新的MyPromise实例对象中的
rejecte
,如果是成功话将这个值存入一个数组 - 存入数组时计数,如果存入的数量达到传入的数组长度,说明调用完毕,执行
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
Promise.resolve方法的实现
关于Promise.resolve()
方法的用法可以参考Promise.resolve()
与Promise.reject()
。
我们实现的思路主要如下:
- 该方法是一个静态方法
- 该方法接受的如果是一个值就将该值包装为一个MyPromise的实例对象返回,如果是一个MyPromise的实例对象,调用then方法返回。
实现代码如下:
?
1 2 3 4 5 6 7 8 |
|
finally方法的实现
关于finally方法可参考finally()
,实现该方法的实现代码如下:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
测试:
?
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 |
|
catch方法的实现
关于catch方法可以参考catch()
,实现该方法其实非常简单,只需要在内部调用then方法,不传递第一个回调函数即可,
实现代码如下:
?
1 2 3 |
|
测试如下:
?
1 2 3 4 5 6 7 8 9 10 |
|
完整代码
?
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 212 213 214 215 216 217 |
|