Promiseコンビネータ

公開日 · タグ ECMAScript ES2020 ES2021

ES2015でPromiseが導入されて以来、JavaScriptは厳密に2つのPromiseコンビネータをサポートしてきました。それは静的メソッドのPromise.allPromise.raceです。

現在、2つの新しい提案が標準化プロセスを進んでいます。それがPromise.allSettledPromise.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以降でサポート

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以降でサポート

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に応じて、計算された結果、またはエラーメッセージを2つの別々のコードパスでレンダリングします。

Promise.allSettled #

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 #

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!');