Promise
异步任务的处理
以一个实际例子作为切入点:
- 调用一个函数,在这个函数中发送网络请求(这里使用定时器模拟);
- 如果网络请求发送成功,那么告知调用者发送成功;
- 如果网络请求发送失败,那么告知调用者发送失败。
在上面的解决方案中,我们确实可以解决请求函数得到结果之后,获取到对应的回调,但是它存在两个主要的问题:
- 需要自己来设计回调函数、回调函数名称、回调函数的使用等;
- 对于不同的人、不同的框架设计出来的方案是不同的,那么我们必须耐心去看别人的源码或者文档,以便可以理解这个函数到底怎么用。
什么是 Promise?
我们来看一下 Promise
的 API 是怎么样的:
Promise
是一个类,可以翻译成承诺、许诺、期约;- 当需要给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个
Promise
的对象; - 在通过
new
创建Promise
对象时,需要传入一个回调函数,我们称之为executor
。- 这个回调函数会被立即执行,并且给传入另两个对调函数
resolve
、reject
; - 当调用
resolve
回调函数时,会执行Promise
对象的then
方法传入的回调函数; - 当调用
reject
回调函数时,会执行Promise
对象的catch
方法传入的回调函数。
- 这个回调函数会被立即执行,并且给传入另两个对调函数
Promise 的代码结构
上面 Promise
使用过程,我们可以将它划分成三个状态:
- 待定(
pending
):初始状态,既没有被兑现,也没有被拒绝,当执行executor
中的代码时,处于该状态; - 已兑现(
fulfilled
): 意味着操作成功完成,执行了resolve
时,处于该状态; - 已拒绝(
rejected
): 意味着操作失败,执行了reject
时,处于该状态。
Promise 重构请求
Executor
Executor 是在创建 Promise
时需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数:
通常我们会在 Executor 中确定我们的 Promise
状态:
- 通过
resolve
,可以兑现(fulfilled
)Promise
的状态,我们也可以称之为已决议(resolved
); - 通过
reject
,可以拒绝(reject
)Promise
的状态。
这里需要注意:一旦状态被确定下来,Promise
的状态会被锁死,该 Promise
的状态是不可更改的。
- 在我们调用
resolve
的时候,如果resolve
传入的值本身不是一个Promise
,那么会将该Promise
的状态变成兑现(fulfilled
); - 在之后我们去调用
reject
时,已经不会有任何的响应了(并不是这行代码不会执行,而是无法改变Promise
状 态)。
resolve 不同值的区别
情况一:如果 resolve
传入一个普通的值或者对象,那么这个值会作为 then
回调的参数;
情况二:如果 resolve
中传入的是另外一个 Promise
,那么这个新 Promise
会决定原 Promise
的状态;
情况三:如果 resolve
中传入的是一个对象,并且这个对象有实现 then
方法,那么会执行该 then
方法,并且根据
then
方法的结果来决定 Promise
的状态。
then 方法 - 接受两个参数
then
方法是 Promise
对象上的一个方法:它其实是放在 Promise
的原型上的 Promise.prototype.then
。
then
方法接受两个参数:
fulfilled
的回调函数:当状态变成fulfilled
时会回调的函数;reject
的回调函数:当状态变成reject
时会回调的函数。
then 方法 - 多次调用
一个 Promise
的 then
方法是可以被多次调用的:
- 每次调用我们都可以传入对应的
fulfilled
回调; - 当
Promise
的状态变成fulfilled
的时候,这些回调函数都会被执行。
then 方法 – 返回值
then
方法本身是有返回值的。它的返回值是一个 Promise
,但是 then
方法返回的 Promise
到底处于什么样的状态呢?
Promise
有三种状态,那么这个 Promise
处于什么状态呢?
- 当
then
方法中的回调函数本身在执行的时候,那么它处于pending
状态; - 当
then
方法中的回调函数返回一个结果时,那么它处于fulfilled
状态,并且会将结果作为resolve
的参数;- 情况一:返回一个普通的值;
- 情况二:返回一个
Promise
; - 情况三:返回一个
thenable
值。
- 当
then
方法抛出一个异常时,那么它处于reject
状态。
catch 方法 - 多次调用
catch
方法也是 Promise
对象上的一个方法:它也是放在 Promise
的原型上的 Promise.prototype.catch
。
一个 Promise
的 catch
方法是可以被多次调用的:
- 每次调用我们都可以传入对应的
reject
回调; - 当
Promise
的状态变成reject
的时候,这些回调函数都会被执行。
catch 方法 - 返回值
事实上 catch
方法也是会返回一个 Promise
对象的,所以 catch
方法后面我们可以继续调用 then
方法或者 catch
方法。
下面的代码,后续是 res
打印,这是因为 catch
传入的回调在执行完后,默认状态依然会是 fulfilled
的。
那么如果我们希望后续继续执行 catch
,那么需要抛出一个异常。
finally 方法
finally
是在 ES9(ES2018)中新增的一个特性:表示无论 Promise
对象无论变成 fulfilled
还是 reject
状态,最终都会被执行的代码。
finally
方法是不接收参数的,因为无论前面是 fulfilled
状态,还是 reject
状态,它都会执行。
resolve 方法
前面我们学习的 then
、catch
、finally
方法都属于 Promise
的实例方法,都是存放在 Promise
的 prototype
上的。下面我们再来学习一下 Promise
的类方法。
有时候我们已经有一个现成的内容了,希望将其转成 Promise
来使用,这个时候我们可以使用 Promise.resolve
方
法来完成。
Promise.resolve
的用法相当于 new Promise
,并且执行 resolve
操作。
resolve
参数的形态:
- 情况一:参数是一个普通的值或者对象;
- 情况二:参数本身是
Promise
; - 情况三:参数是一个
thenable
。
reject 方法
reject
方法类似于 resolve
方法,只是会将 Promise
对象的状态设置为 reject
状态。
Promise.reject
的用法相当于 new Promise
,只是会调用 reject
。
Promise.reject
传入的参数无论是什么形态,都会直接作为 reject
状态的参数传递到 catch
的。
all 方法
还有一个类方法是 Promise.all
。它的作用是将多个 Promise
包裹在一起形成一个新的 Promise
。
新的 Promise
状态由包裹的所有 Promise
共同决定:
- 当所有的
Promise
状态变成fulfilled
状态时,新的Promise
状态为fulfilled
,并且会将所有Promise
的返回值组成一个数组; - 当有一个
Promise
状态为reject
时,新的Promise
状态为reject
,并且会将第一个reject
的返回值作为参数。
allSettled 方法
all
方法有一个缺陷:当有其中一个 Promise
变成 reject
状态时,新 Promise
就会立即变成对应的 reject
状态。那么对于 resolved
的,以及依然处于 pending
状态的 Promise
,我们是获取不到对应的结果的;
在 ES11(ES2020)中,添加了新的 API Promise.allSettled
:
- 该方法会在所有的
Promise
都有结果(settled
),无论是fulfilled
,还是reject
时,才会有最终的状态; - 并且这个
Promise
的结果一定是fulfilled
的。
我们来看一下打印的结果:
allSettled
的结果是一个数组,数组中存放着每一个Promise
的结果,并且是对应一个对象的;- 这个对象中包含
status
状态,以及对应的value
值。
race 方法
如果有一个 Promise
有了结果,我们就希望决定最终新 Promise
的状态,那么可以使用 race
方法。prace
是竞技、竞赛的意思,表示多个 Promise
相互竞争,谁先有结果,那么就使用谁的结果。
any 方法
any
方法是 ES12 中新增的方法,和 race
方法是类似的:
any
方法会等到一个fulfilled
状态,才会决定新Promise
的状态;- 如果所有的
Promise
都是reject
的,那么也会等到所有的Promise
都变成rejected
状态。
如果所有的 Promise
都是 reject
的,那么会报一个 AggregateError
的错误。
手写 Promise
简单总结手写 Promise
一. Promise 规范
二. Promise 类设计
三. 构造函数的规划
四. then 方法的实现
五. catch 方法
六. finally
七. resolve/reject
八. all/allSettled
核心:要知道 new Promise
的 resolve、reject
在什么情况下执行。
all:
- 情况一:所有的都有结果;
- 情况二:有一个
reject
。
allSettled:
- 情况:所有都有结果,并且一定执行
resolve
。
九. race/any
race:
- 情况:只要有结果。
any:
- 情况一:必须等到一个
resolve
结果; - 情况二:都没有
resolve
,所有的都是reject
。