JavaScript 在 2020 年能给我们带来什么?
自 ES2015 起,Promise 被引入,用于简化异步操作的处理。Promise 有两个常用的组合器:` allawait` 和 `await` race。它们都很有用,但并不能涵盖所有用例。如果我们想要等待所有 Promise 都完成,即使其中一些会出错,或者只需要第一个成功的操作,该怎么办?接下来,我将介绍新的 Promise 组合器,它们将帮助你找到这些问题的答案。
首先,我们将看看我们已经拥有的组合器,然后再看看即将推出的两个组合器。
Promise.all(文档)
组合all器接受多个 Promise(通常是迭代器或数组),并返回一个 Promise,该 Promise 会在所有 Promise 都完成或迭代器为空时解析。给定的 Promise 没有执行顺序,它们并行执行。但是,每个输入 Promise 的返回值顺序与输入顺序一致。返回的 Promise 将包含一个数组,其中包含所有输入的值。
const first = Promise.resolve('Batman');
const second = Promise.resolve('Joker');
Promise
.all([first, second])
.then(results => {
// results = ['Batman', 'Joker']
});
如果其中一个 Promise 被拒绝会发生什么?返回的 Promise 会被拒绝,我们将无法知道其他 Promise 发生了什么。
const first = Promise.resolve('Batman');
const second = Promise.reject(new Error('Joker'));
Promise
.all([first, second])
.then(results => {
// we won't get here
})
.catch(error => {
// Error: Joker
});
即使其中一些承诺会兑现,我们也不能坐等所有承诺都兑现。
Promise.race(文档)
该race函数返回的 Promise 会在传递的 Promise 中的任何一个被解析或拒绝后立即被执行。如果您只关心第一个结果而想忽略那些执行较慢的结果,这将非常有用。
const first = Promise.resolve('Batman');
const second = Promise.resolve('Joker');
Promise
.race([first, second])
.then(result => {
// result = 'Batman' or 'Joker'
});
当其中一个承诺被拒绝时,也会发生同样的情况。
const first = Promise.resolve('Batman');
const second = Promise.reject(new Error('Joker'));
Promise
.race([first, second])
.then(result => {
// we get here
})
.catch(error => {
// or here
});
如果前一个承诺被拒绝,我们不能等待第一个承诺得到解决。
未来
即将发布的版本将提供两个新的组合器,帮助我们克服现有all组合器的局限性race。新版本还将引入新的方法,以便更轻松地处理失败的操作。
Promise.allSettled(文档)
组合all器接收多个 Promise,并返回一个 Promise,该 Promise 会在所有输入都完成或拒绝时解析。与all组合器的主要区别在于,即使其中一个 Promise 被拒绝,它也不会被拒绝。allSettled它会等待所有其他 Promise 完成,并返回已完成和已拒绝的 Promise 结果。
const first = Promise.resolve('Batman');
const second = Promise.reject(new Error('Joker'));
Promise
.allSettled([first, second])
.then(results => {
// results = [
{ status: 'fulfilled', value: 'Batman' },
{ status: 'rejected', reason: Error: Joker }
]
});
对于每个已完成的 Promise,我们会得到一个包含 ` statusreturn` 属性的对象fulfilled,value该属性存储的是 Promise 的返回值。对于已拒绝的 Promise,我们会得到一个包含 `error`status属性的对象rejected,该reason属性存储的是 Promise 的错误信息。
如果要区分已拒绝的承诺和已完成的承诺,则需要对结果数组进行快速筛选。
Promise.any(文档)
函数返回的 Promiseany会一直等待,直到其中一个提供的 Promise 解析成功。即使部分 Promise 失败,它最终也会解析成功。如果所有 Promise 都拒绝,race则函数也会被拒绝。
const first = Promise.resolve('Batman');
const second = Promise.reject(new Error('Joker'));
Promise
.any([first, second])
.then(result => {
// result = 'Batman'
});
这是一种很好的方法,即等待第一个成功的操作完成后再忽略其余的操作。
Promise.prototype.finally(文档)
Promise 有两种状态:已实现 (fulfilled) 或已拒绝 (rejected)。当 Promise 已实现时then,会执行回调函数;当 Promise 被拒绝时,会catch执行回调函数。如果我们想在之后运行一些清理命令该怎么办?我们需要在两个回调函数中都包含这些命令。而这正是该finally方法发挥作用的地方,因为它在两种情况下都会被调用。
const second = Promise.reject(new Error('Joker'));
second
.then(result => {})
.catch(error => {})
.finally(() => {
// do some cleanup
});
承诺。
该方法try接收一个可以抛出同步错误并返回被拒绝的 Promise 的函数,并将这两种类型都转换为被拒绝的 Promise。
Promise.try(() => {
throw new Error('Joker'); // synchronous
return Promise.reject(new Error('Joker')) // asynchronous
});
在执行异步操作之前先执行同步操作时,例如在命令行脚本中,它会非常有用。如果是同步代码,错误处理通常放在 try-catch 块中。异步代码的错误处理则放在 catch 回调中。使用后Promise.try,您无需单独处理错误。
如果这种方法仍然不够精确,我建议您阅读这篇关于该主题的详细文章。
可用性
Promise.prototype.finally这些功能Promise.allSettled在所有现代浏览器和 Node.js(版本 12 及更高版本)中均可用,但其他功能仍处于草案阶段。我们需要相应的兼容层才能使用它们。
require('promise.allSettled').shim();
require('promise.any').shim();
require('promise.finally').shim();
require('promise.try').shim();
您可以在ECMAScript shims下找到它们。
概括
Promise 的出现极大地推动了异步代码的组织,使其更加便捷易读。如今,TC39 委员会正在致力于使其更加自然流畅。最终成果是两个新的组合器函数(allSettled`and` 和any`),以及 ` finallyand` 方法和 ` tryand` 方法。如果您喜欢这些方法,不妨立即使用我们提供的 shim 来体验它们。