ES2015でPromiseが導入されて以来、JavaScriptは厳密に2つのPromiseコンビネータをサポートしてきました。それは静的メソッドのPromise.all
とPromise.race
です。
現在、2つの新しい提案が標準化プロセスを進んでいます。それがPromise.allSettled
とPromise.any
です。これらの追加により、JavaScriptには合計4つのPromiseコンビネータが存在することになり、それぞれが異なるユースケースを可能にします。
4つのコンビネータの概要は以下のとおりです。
名前 | 説明 | ステータス |
---|---|---|
Promise.allSettled | ショートサーキットしない | ES2020で追加 ✅ |
Promise.all | 入力値が拒否されたときにショートサーキットする | ES2015で追加 ✅ |
Promise.race | 入力値が確定したときにショートサーキットする | ES2015で追加 ✅ |
Promise.any | 入力値が成功したときにショートサーキットする | ES2021で追加 ✅ |
各コンビネータのユースケースの例を見てみましょう。
Promise.all
#
- Chrome: バージョン32以降でサポート
- Firefox: バージョン29以降でサポート
- Safari: バージョン8以降でサポート
- Node.js: バージョン0.12以降でサポート
- Babel: サポート
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
#
- Chrome: バージョン32以降でサポート
- Firefox: バージョン29以降でサポート
- Safari: バージョン8以降でサポート
- Node.js: バージョン0.12以降でサポート
- Babel: サポート
Promise.race
は、複数のPromiseを実行し、以下のいずれかを実行したい場合に役立ちます。
- 最初に入ってくる成功した結果で何かをする(Promiseのいずれかが成功した場合)、または
- Promiseのいずれかが拒否されたらすぐに何かをする。
つまり、Promiseのいずれかが拒否された場合、エラーケースを個別に処理するために、その拒否を保持したいとします。次の例はまさにそれを行っています。
try {
const result = await Promise.race([
performHeavyComputation(),
rejectAfterTimeout(2000),
]);
renderResult(result);
} catch (error) {
renderError(error);
}
時間がかかる可能性のある計算コストの高いタスクを開始しますが、2秒後に拒否されるPromiseと競合させます。最初に成功または拒否されたPromiseに応じて、計算された結果、またはエラーメッセージを2つの別々のコードパスでレンダリングします。
Promise.allSettled
#
- Chrome: バージョン76以降でサポート
- Firefox: バージョン71以降でサポート
- Safari: バージョン13以降でサポート
- Node.js: バージョン12.9.0以降でサポート
- Babel: サポート
Promise.allSettled
は、すべての入力Promiseが確定したときに信号を送ります。これは、Promiseが成功したか拒否されたかを意味します。これは、Promiseの状態を気にせず、成功したかどうかに関係なく、作業が完了したことを知りたい場合に役立ちます。
たとえば、一連の独立したAPI呼び出しを開始し、Promise.allSettled
を使用して、ローディングスピナーを削除するなど、他のことを行う前にすべてが完了していることを確認できます。
const promises = [
fetch('/api-call-1'),
fetch('/api-call-2'),
fetch('/api-call-3'),
];
// Imagine some of these requests fail, and some succeed.
await Promise.allSettled(promises);
// All API calls have finished (either failed or succeeded).
removeLoadingIndicator();
Promise.any
#
- Chrome: バージョン85以降でサポート
- Firefox: バージョン79以降でサポート
- Safari: バージョン14以降でサポート
- Node.js: バージョン16以降でサポート
- Babel: サポート
Promise.any
は、Promiseのいずれかが成功したらすぐに信号を送ります。これはPromise.race
に似ていますが、any
は、Promiseの1つが拒否されても早期に拒否されません。
const promises = [
fetch('/endpoint-a').then(() => 'a'),
fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
// Any of the promises was fulfilled.
console.log(first);
// → e.g. 'b'
} catch (error) {
// All of the promises were rejected.
console.assert(error instanceof AggregateError);
// Log the rejection values:
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], 'Stuff went wrong!');