跳到主要内容

Promise 组合器

· 阅读需 4 分钟
Mathias Bynens ([@mathias](https://twitter.com/mathias))

自从 ES2015 引入 Promise 后,JavaScript 支持的 Promise 组合器只有两个:静态方法 Promise.allPromise.race

目前有两个新提案正在进行标准化过程:Promise.allSettledPromise.any。随着这些新增内容,JavaScript 中将总共有四种 Promise 组合器,每种都支持不同的使用场景。

以下是这四种组合器的概述:

名称描述状态
Promise.allSettled不会短路已添加于 ES2020 ✅
Promise.all当输入值被拒绝时短路已添加于 ES2015 ✅
Promise.race当输入值解决时短路已添加于 ES2015 ✅
Promise.any当输入值被履行时短路已添加于 ES2021 ✅

让我们来看看每种组合器的一个示例使用场景。

Promise.all

Promise.all 可让你知道所有输入的 Promise 都已被履行,或者其中一个被拒绝时的情况。

假设用户点击了按钮,你想加载一些样式表以便呈现一个全新的 UI。这个程序并行启动了每个样式表的 HTTP 请求:

const promises = [
fetch('/component-a.css'),
fetch('/component-b.css'),
fetch('/component-c.css'),
];
try {
const styleResponses = await Promise.all(promises);
enableStyles(styleResponses);
renderNewUi();
} catch (reason) {
displayError(reason);
}

你只希望在所有请求都成功后开始渲染新的 UI。如果出现问题,你还希望尽快显示错误信息,而无需等待其他工作完成。

在这种情况下,你可以使用 Promise.all:你希望知道所有 Promise 是否被履行,_或者_其中一个是否被拒绝。

Promise.race

Promise.race 很有用,当你希望运行多个 Promise,并且…

  1. 对于第一个成功的结果采取某些行动(当其中一个 Promise 被履行时),或者
  2. 当其中一个 Promise 被拒绝时就采取行动。

也就是说,如果其中一个 Promise 被拒绝,你希望保留该拒绝以单独处理错误情况。以下示例正是这种情况:

try {
const result = await Promise.race([
performHeavyComputation(),
rejectAfterTimeout(2000),
]);
renderResult(result);
} catch (error) {
renderError(error);
}

我们启动了一个可能耗时较长的计算任务,但将它与一个 2 秒后被拒绝的 Promise 竞争。根据第一个 Promise 的履行或拒绝,我们可以在两条单独的代码路径中分别渲染计算结果或错误信息。

Promise.allSettled

Promise.allSettled 可在所有输入的 Promise 均已解决(即被履行或被拒绝)时发出信号。在不关心 Promise 状态的情况下,这非常有用,你只希望知道工作已经完成,而不管是否成功。

例如,你可以启动一系列独立的 API 调用,并使用 Promise.allSettled 来确保它们全部完成后再执行其他操作,比如移除加载指示器:

const promises = [
fetch('/api-call-1'),
fetch('/api-call-2'),
fetch('/api-call-3'),
];
// 假设其中一些请求失败,另一些成功。

await Promise.allSettled(promises);
// 所有 API 调用都已完成(无论失败还是成功)。
removeLoadingIndicator();

Promise.any

Promise.any 可以在其中一个 promise 完成时立即给予信号。这类似于 Promise.race,但 any 不会在某个 promise 拒绝时提前失败。

const promises = [
fetch('/endpoint-a').then(() => 'a'),
fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
// 任意一个 promise 被 fulfilled。
console.log(first);
// → 例如 'b'
} catch (error) {
// 所有的 promise 都被拒绝。
console.assert(error instanceof AggregateError);
// 记录拒绝的值:
console.log(error.errors);
// → [
// <TypeError: Failed to fetch /endpoint-a>,
// <TypeError: Failed to fetch /endpoint-b>,
// <TypeError: Failed to fetch /endpoint-c>
// ]
}

此代码示例检查哪个端点响应最快,并打印日志。只有当 所有 请求都失败时,我们才会进入 catch 块,在那里可以处理错误。

Promise.any 的拒绝可能同时代表多个错误。为了在语言层面支持这一点,引入了一种称为 AggregateError 的新错误类型。除了在上述示例中的基本使用,AggregateError 对象还可以像其他错误类型那样以编程方式构造:

const aggregateError = new AggregateError([errorA, errorB, errorC], '发生了一些错误!');