メインコンテンツまでスキップ

Promiseの組み合わせ

· 約5分
Mathias Bynens ([@mathias](https://twitter.com/mathias))

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

現在、標準化プロセスを進行中の2つの新しい提案があります: Promise.allSettledPromise.anyです。この追加により、JavaScriptには合計4つのPromiseコンビネーターが存在し、それぞれ異なるユースケースを可能にします。

ここでは4つのコンビネーターの概要を紹介します:

名前説明ステータス
Promise.allSettled短絡しないES2020で追加 ✅
Promise.all入力値が拒否された場合に短絡ES2015で追加 ✅
Promise.race入力値が解決された場合に短絡ES2015で追加 ✅
Promise.any入力値が成功した場合に短絡ES2021で追加 ✅

各コンビネーターの例を見てみましょう。

Promise.all

Promise.allは、すべての入力Promiseが成功した時、またはそのうちの1つが拒否された時に反応します。

例えば、ユーザーがボタンをクリックして完全に新しい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が成功するのを待つか、1つが拒否された時点で通知を受け取ることができます。

Promise.race

Promise.raceは、複数のPromiseを実行して以下のいずれかを実現したい場合に役立ちます:

  1. 最初に成功した結果に対応する (Promiseが成功した場合)、または
  2. 最初に拒否されたPromiseがある場合、ただちに対応する。

つまり、Promiseの1つが拒否された場合、その拒否を保存してエラーケースを別途処理したい場合。この例ではその処理を行います:

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.raceに似ていますが、anyはプロミスの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);
// いずれかのプロミスが完了しました。
console.log(first);
// → 例えば 'b'
} catch (error) {
// すべてのプロミスが拒否されました。
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という新しいエラータイプが導入されました。このタイプは上記の例での基本的な使用法に加えて、他のエラータイプと同様にプログラムで構築することもできます:

const aggregateError = new AggregateError([errorA, errorB, errorC], '何かがうまくいかなかった!');