关于 Promise 你需要知道的一切
Promise
是 JavaScript 中的对异步操作最佳的 API 之一。作为 JavaScript 开发人员,需要熟练掌握 Promise
相关的知识。本文就来总结一下这些知识点,先来回顾 JavaScript 过去异步操作的处理方式?然后详细介绍 Promises 对象及相关的方法。
现在先来了解一下 JavaScript 异步概念。
异步回顾
什么是异步?如果在函数返回的时候,调用者还不能获取到预期结果,而是将来通过一定的方式得到(例如回调函数),这函数就是异步。
异步回调
异步回调,就是常见的 callback
,这是过去在 JavaScript 中处理异步操作的常见方式,如 AJAX ,发起一个 HTTP 请求,具体服务器什么时候返回响应数据取决于客户的环境,存在很多不确定因素,这时采用回调函数可以在返回响应数据的时候触发回调函数。
这种方式当有多个请求的情况,后续请求需要等待前面请求的响应数据的时候,就会出现常见的回调地狱。
出现回调地狱将会给项目开发带来很多问题:
会导致逻辑混乱,耦合性高,改动一处就会导致全部变动,嵌套多时,BUG 问题难以发现。
不能使用
try...catch
来捕获异常。不能使用
return
返回真实数据
为了避免回调地狱的出现,后来就有了 Promise
对象。
Promise 的工作方式
promise
对象是一个可以从异步函数同步返回的对象,它将处于 3 种可能的状态之一:
fulfilled
已兑现: onFulfilled()
将被调用,即操作完成(例如,resolve()
被调用)rejected
已拒绝: onRejected()
将被调用,即操作失败(例如,reject()
被调用)pending
待定:初始状态,既没有被兑现,也没有被拒绝
如果一个 promise
没有挂起(它已被解决或拒绝),它就会被解决。有时使用已解决和已解决表示同一件事:不是 pending
。
promise
一旦确定,就不能再重新确定,再次调用 resolve()
或 reject()
将没有效果。一个已确定的 promise
的具有不可变性。
对于 promise
的状态监控可以使用承诺链,即在 fulfilled
已兑现的状态可以使用 then
方法可以获取已兑现的结果,在rejected
已拒绝状态使用 catch
方法获取拒绝的原因。
看起来比 callback
的方式优雅一点,对于需要发起多次 HTTP 请求才能完整呈现的需求,代码如下:
上面代码是否有种似曾相识的感觉,原本是为了解决回调地狱,但似乎理想跟现实还是有差距。
于是 ES2021 为 Promise 对象增加新的特征,其中包括:Promise.any()
、Promise.all()
、Promise.allSettled()
、Promise.race()
。
Promise.any()
Promise.any(promises)
能够并行运行 promise
,并解析为 promises
列表中第一个成功解析的 promise
的值。需要注意的是 Promise.any()
方法依然是实验性的,尚未被所有的浏览器完全支持。
下面来看看 Promise.any()
是如何工作的。
1.工作原理
Promise.any()
可用于以并行和竞争方式执行独立的异步操作,以获取任何第一个完成的 promise
的值。
该函数接受一个 promise
数组(通常为一个可迭代对象)作为参数,如下:
当输入 promises
中的第一个 promise
被执行完成时,anyPromise
会立即解析为该 promise
的值。
可以使用 then
方法提取第一个 promise
的值:
也可以使用 async/await
语法:
Promise.any()
返回的 promise
与任何第一个执行的 promise
一起执行。即使某些 promise
被 rejected
,这些 rejections
也将会被忽略。
但是,如果输入数组中的所有 promises
都被拒绝,或者输入数组为空,那么 Promise.any()
会 rejected
包含输入的 promises
执行的 rejection
错误原因集合。
2. 使用指南
现在来深入介绍一下 Promise.any()
, 在这之前,先来定义 2 个简单的函数。
函数
resolveTimeout(value, delay)
将返回一个在经过delay
时间后有resolve
的promise
。
函数
rejectTimeout(reason, delay)
将返回一个在经过delay
时间后有reject
的promise
。
接下来使用上面定义的 2 个辅助函数来试试 Promise.any()
。
2.1 完成所有 promises
下面尝试运行第一个解析列表:
promise .any([…])
返回一个在 1秒内
解析到数组 fruits
的 promise
,因为解析 fruits 的 promise
先执行完成。
第二个是 2秒内
解析到数组 vegetables
的 promise
,其值将被忽略。
2.2 一个 promise
被 rejected
将上面第一个 promise
出现异常被 rejected
,如下代码:
上面的代码,第一个 promise
在 1秒后
被rejected
,从执行的结果不难看出 Promise.any()
跳过了第一个被rejected
的promise
,等待第二个 2秒后
执行完成的promise
。
2.3 所有的 promises
被 rejected
下面来看下当所有的 promises
被 rejected
会出现什么结果,如下代码:
从上面代码的执行结果来看,当所有输入promises
被 rejected
后, Promise.any([...])
将返回一种特殊的错误 AggregateError
而被 rejected
,而详细的 rejected
原因在属性 aggregateError.errors
中 。
小结
Promise.any()
可用于以竞争方式并行执行独立的异步操作,以获取任何第一个成功执行完成的 promise
的值。如果 Promise.any()
的所有输入 promise
都被rejected
后,那么辅助函数返回的 promise
也会以错误集合的方式拒绝,该错误在一个特殊属性 AggregateError
中包含输入 promise
的拒绝原因:aggregateError.errors
。
Promise.all()
方法 Promise.all(promises)
,能够一次并行处理多个 promise
,并且只返回一个 promise
实例, 那个输入的所有 promise
的 resolve
回调的结果是一个数组。
下面来看看 Promise.all()
是如何工作的。
1.工作原理
Promise.all()
是一个内置的辅助函数,接受一组 promise
(或者一个可迭代的对象),并返回一个promise
:
可以使用 then
方法提取第一个 promise
的值:
也可以使用 async/await
语法:
Promise.all()
返回的 promise
被解析或拒绝的方式。
如果 allPromise
都被成功解析,那么 allPromise
将使用一个包含各个 promise
已执行完成后的值的数组作为结果。数组中 promise
的顺序是很重要的——将按照这个顺序得到已实现的值。
但是如果至少有一个 promise
被 rejected
,那么 allPromise
会以同样的原因立即 rejected
(不等待其他 promise
的执行)。
如果所有的 promise
被 rejected
,等待所有的promise
执行完成,但只会返回最先被rejected
的promise
的 reject
原因。
2. 使用指南
现在来深入介绍一下 Promise.all()
, 在这之前,先来定义 2 个简单的函数。
函数
resolveTimeout(value, delay)
将返回一个在经过delay
时间后有resolve
的promise
。
函数
rejectTimeout(reason, delay)
将返回一个在经过delay
时间后有reject
的promise
。
接下来使用上面定义的 2 个辅助函数来试试 Promise.all()
。
2.1 完成所有 promises
下面定义了一个 promise
数组 allPromise
,所有的 promise
都能够成功的 resolve
值,如下:
从上面执行的结果来看 Promise.all()
返回的 promise
的 resolve
数组是按照执行前 allPromise
的顺序组成其结果。
promise
数组的顺序直接影响结果的顺序,和promise
执行完成的先后无关。
2.2 一个 promise
被 rejected
将上面数组 allPromise
的第一个 promise
出现异常被 rejected
,如下代码:
然而,在经过 5秒
之后,第一个 promise
由于异常被 rejected
,使得 allPromise
也被 rejected
,并返回跟第一个 promise
一样的错误信息:Error: fruits is empty
,即使在 1秒
后就完成的第二个 promise
的值也不被采纳。
接下来将数组 allPromise
的所有 promise
都抛出异常被 rejected
,通过定时器将 rejected
的顺序做个调整,如下:
经过 5秒
之后完成执行,而结果显示为 Error: vegetables is empty
,不难看出 allPromise
被 rejected
的原因是最先 rejected
的promise
。
Promise.all()
的这种行为被称为快速失败,如果promise
数组中至少有一个promise
被rejected
,那么返回的promise
也被拒绝。如果promise
数组中所有的都被rejected
,那么返回的promise
被拒绝的原因是先rejected
的那一个。
小结
Promise.all()
是并行执行异步操作并获取所有 resolve
值的最佳方法,非常适合需要同时获取异步操作结果来进行下一步运算的场合。
Promise.allSettled()
方法 Promise.allSettled(promises)
,返回一个在所有给定的 promise
都已经 fulfilled
或 rejected
后的 promise
,并带有一个对象数组,每个对象表示对应的promise
结果。
下面来看看 Promise.allSettled()
是如何工作的。
1.工作原理
Promise.allSettled()
可用于并行执行独立的异步操作,并收集这些异步操作的结果。
函数接受一个 promise
数组(或通常是一个可迭代的)作为参数,如下:
当所有输入 promises
都被履行或拒绝时,statusesPromise
会解析为一个具有其状态的数组:
{ status: 'fulfilled', value:value }
: 如果相应的promise
已经履行{ status: 'rejected', reason: reason }
:如果相应的promise
被拒绝
可以使用 then
方法提取所有 promises
的状态:
也可以使用 async/await
语法:
Promise.allSettled()
返回的承诺总是以一系列状态实现,无论是否有一些(或者全部)输入承诺被拒绝。
Promise.allSettled()
和Promise.all()
的最大不同:Promise.allSettled()
永远不会被rejected
。
2. 使用指南
现在来深入介绍 Promise.allSettled()
的使用之前, 还是先来定义 2 个简单的函数。
接下来使用上面定义的 2 个辅助函数来试试 Promise.allSettled()
。
2.1 完成所有 promises
下面定义了一个 promise
数组 statusesPromise
,所有的 promise
都能够成功的 resolve
值,如下:
从上面执行的结果来看 Promise.allSettled()
返回的一个 promise
的 resolve
状态数组是按照执行前 statusesPromise
的顺序组成其结果。
2.2 一个 promise
被 rejected
将上面第一个 promise
出现异常被 rejected
,如下代码:
即使输入数组中的第二个 promise
被 rejected
, statusesPromise
仍然可以成功解析状态数组。
2.3 所有 promises
被 rejected
将上面所有的 promises
出现异常被 rejected
,如下代码:
小结
当需要执行并行和独立的异步操作并收集所有结果时,Promise.allSettled()
就是不错的选择,即使一些异步操作可能失败。
Promise.race()
方法 Promise.race(promises)
,顾名思义就是赛跑的意思,Promise.race([p1, p2, p3])
里面 promise
数组那个执行完成得快就获取那个的结果,不管结果本身是成功履行状态还是失败拒绝状态,只输出最快的 promise
。
下面来看看 Promise.race()
是如何工作的。
1.工作原理
Promise.race()
返回一个 promise
,一旦迭代器中的某个 promise
履行或拒绝,返回的 promise
就会履行或拒绝。
函数接受一个 promise
数组(或通常是一个可迭代的)作为参数,如下:
当所有输入 promises
中有一个 promise
快速被履行或拒绝时,racePromise
就会解析快速完成的 promise
结果(履行或拒绝):
可以使用 then
方法提取 racePromise
的结果:
也可以使用 async/await
语法:
Promise.race()
返回的承诺和最先完成的承诺信息一致。
Promise.race()
和Promise.any()
的不同:Promise.race()
承诺列表中寻找第一个履行或拒绝的承诺;Promise.any()
是从承诺列表中查找第一个履行的承诺。
2. 使用指南
现在来深入介绍 Promise.race()
的使用之前,同样先来定义 2 个简单的函数。
接下来使用上面定义的 2 个辅助函数来试试 Promise.race()
。
2.1 完成所有 promises
下面定义了一个 promise
数组 racePromise
,所有的 promise
都能够成功的 resolve
值,如下:
从上面执行的结果来看 Promise.race()
返回最快履行的 promise
的 resolve
结果。
2.2 一个 promise
被 rejected
将上面第一个 promise
出现异常被 rejected
,如下代码:
从上面的结果看,最先完成的 promise
被 rejected
,那么 fastPromise
返回的 promise
也是被 rejected
。
下面将rejected
的承诺时间延长到 5 秒,如下:
从上面执行结果看到,最快完成的 promise
履行了 resolve
,那么 fastPromise
返回的 promise
也是履行了 resolve
。
2.3 所有 promises
被 rejected
将上面所有的 promises
出现异常被 rejected
,如下代码:
从结果来看,虽然两个承诺都被拒绝了,fastPromise
返回的 promise
是最快被拒绝的 。
3. 使用场景
3.1. 性能测试
在有异步操作的项目中,在对于网络或数据库请求进行性能进行优化的时候,可以使用 Promises
来测试其优化效果,通过使用 Promise.race()
来测试两种不同的方法的响应速度。
3.2 最佳选择
例如获取同一类型的数据有多个请求服务器,同时向多个服务器发送请求,只要其中一个完成工作,就将其数据呈现,达到选择最佳线路的效果。这是可以使用 Promise.race()
同时执行 promise
并在第一个成功后立即完成。
小结
Promise.race()
为第一个已解决和已拒绝的 promise
执行回调函数,而 Promise.any()
为第一个已履行的 promise
执行回调函数,如果没有履行的 promise
则拒绝一个特殊属性 AggregateError
。
版权声明: 本文为 InfoQ 作者【devpoint】的原创文章。
原文链接:【http://xie.infoq.cn/article/ece78eb6dae1bf9eb81e153a8】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论