Promise 是 JavaScript 引入的最受赞誉的功能之一。在语言中直接内置了原生异步构件,开创了一个新时代,不仅改变了我们编写代码的方式,也为其他优秀的 API(如 fetch
!)奠定了基础。
让我们退一步,回顾一下它们最初发布时获得的功能,以及接下来将获得哪些新功能。
不了解 Promise 的概念?我强烈推荐 Jake Archibald 的文章 作为入门指南。
我们今天拥有的功能
让我们快速了解一下目前可以使用 Promise 执行的一些操作。当 JavaScript 引入 Promise 时,它为我们提供了一个 API 来执行异步操作并对它们的成功返回或失败做出反应,一种围绕某些数据或结果创建关联的方式,而我们仍然不知道其值。
以下是我们今天拥有的 Promise 功能。
处理 Promise
每次异步方法返回一个 Promise 时(例如,当我们使用 fetch 时),我们可以使用管道 then()
在 Promise *fulfilled*(已完成)时执行操作,并使用 catch()
对 Promise *rejected*(已拒绝)做出响应。
fetch('//resource.to/some/data')
.then(result => console.log('we got it', result.json()))
.catch(error => console.error('something went wrong', error))
经典用例是从 API 调用数据,并在数据返回时加载数据,或者如果找不到数据则显示错误消息。
此外,在其初始版本中,我们获得了两种处理 Promise 组的方法。
解析和拒绝 Promise 集合
当 Promise 成功解析时,它可以被 *fulfilled*(已完成),当它以错误解析时,它可以被 *rejected*(已拒绝),并且在没有解析时,它处于 *pending*(挂起)状态。当 Promise 已解析时,无论结果如何,它都被认为是 settled(已完成)。
因此,我们有两种方法可以帮助处理 Promise 组的行为,具体取决于我们获得的状态组合。
Promise.all
就是其中一种方法。只有当所有 Promise 都成功解析时,它才会 fulfilled(已完成),并返回一个包含每个 Promise 结果的数组。如果其中一个 Promise 失败,Promise.all
将进入 catch
并返回错误原因。
Promise.all([
fetch('//resource.to/some/data'),
fetch('//resource.to/more/data')
])
.then(results => console.log('We got an array of results', results)
.catch(error => console.error('One of the promises failed', error)
在这种情况下,Promise.all
将在集合的成员之一抛出错误时短路并进入 catch
,或者在所有 Promise 都 *fulfilled*(已完成)时 settled(已完成)。
查看 Domenic Denicola 关于 Promise 状态的这篇 简短文章,以更详细地了解其措辞和概念。
我们还有 Promise.race
,它会立即解析它获得的第一个 Promise,无论它是 fulfilled(已完成)还是 rejected(已拒绝)。第一个 Promise 解析后,其余 Promise 将被忽略。
Promise.race([
fetch('//resource.to/some/data'),
fetch('//resource.to/other/data')
])
.then(result => console.log('The first promise was resolved', result))
.catch(reason => console.error('One of the promises failed because', reason))
新成员
好的,我们将把注意力转向可以期待的新的 Promise 功能。
Promise.allSettled
下一个提议引入的成员是 Promise.allSettled
,顾名思义,它只在数组中所有集合成员不再处于 pending(挂起)状态时继续执行,无论它们是 *rejected*(已拒绝)还是 *fulfilled*(已完成)。
Promise.allSettled([
fetch('//resource.to/some/data'),
fetch('//resource.to/more/data'),
fetch('//resource.to/even/more/data')
])
.then(results => {
const fulfilled = results.filter(r => r.status === 'fulfilled')
const rejected = results.filter(r => r.status === 'rejected')
})
请注意,这与 Promise.all
不同,因为我们永远不会进入 catch
语句。如果我们正在等待将进入 Web 应用程序不同部分的数据集,但希望为每个结果提供更具体的讯息或执行不同的操作,这将非常有用。
Promise.any
下一个新方法是 Promise.any
,它允许我们对集合中任何 fulfilled(已完成)的 Promise做出反应,但只有在它们都失败时才会短路。
Promise.any([
fetch('//resource.to/some/data'),
fetch('//resource.to/more/data'),
fetch('//resource.to/even/more/data')
])
.then(result => console.log('a batch of data has arrived', result))
.catch(() => console.error('all promises failed'))
这有点像 Promise.race
,但 Promise.race
会在第一次解析时短路。因此,如果集合中的第一个 Promise 以错误解析,Promise.race
将继续执行。Promise.any
将继续等待数组中其余项目解析,然后再继续执行。
演示
其中一些使用视觉效果更容易理解,因此我创建了一个 小型游乐场,展示了新方法和现有方法之间的区别。
总结
尽管它们仍处于提案阶段,但社区脚本可以模拟本文中介绍的新方法。像 Bluebird 的 any
和 reflect
就是在等待浏览器支持改进期间的良好 polyfill。
它们还展示了社区如何已经使用这种异步模式,但将它们内置将为 Web 应用程序中的数据获取和异步解析开辟新的可能性。
除了 then 和 catch 之外,您还可以将 finally 管道化到 Promise 中,Sarah Drasner 撰写了 一篇详细的文章,您可以查看。
如果您想了解更多关于即将推出的 Promise
组合器的信息,V8 博客刚刚发布了一个简短的解释,其中包含指向官方规范和提案的链接。
Promise.finally() 也是一个非常有用的功能。
当您在 .then() 和 .catch() 中有重复代码时,它会派上用场,通过将该部分移至 finally() 语句中,您可以保持代码 DRY(Don't Repeat Yourself)。
是的,
finally
是一个有用的处理程序,但这篇文章主要关注 Promise 组合器而不是处理程序,Sarah 已经在 CSS Tricks 中写过关于它的文章,我将在文章中添加一个提及。感谢您的评论!@jmenichelli:您如何提前了解即将推出的功能?
如果您知道此类页面和博客,我也可以从中查看此类内容,请告诉我。
这取决于您通常如何获取有关 Web 技术的信息,我经常使用 Twitter,因此我关注 V8 团队帐户及其工程师,以了解他们在 Chrome 上正在进行的工作、迭代或实现。社区成员还有一些 RSS Feed 和时事通讯,您可以订阅并在电子邮件收件箱中接收它们。
我在规范中找不到使用 Promise.allSettled().catch() 会发生什么。是否必须在 then() 中处理错误?FolktaleJS 提供了一个作为 Promise 抽象的 Task,以及一个 waitAny() 接口,它会在所有任务完成后等待,然后返回第一个错误或已解析的任务返回值数组。这与 Promise.allSettled() 相同吗?
我一开始也觉得这有点棘手。在我的早期测试中,它总是会将
then
作为数组的一部分返回,你之后可以过滤并检查reason
属性。我不知道除了Promise
调用拒绝处理程序之外,抛出的错误是否会将你发送到catch,我猜它不会。