
Promise.any
Promise.any
AggregateError
AggregateError
Promise.all
Promise.all
Promise.allSettled
Promise.allSettled
Promise.race
Promise.race
私は数ヶ月間、Promise.anyを無視していました。なぜなら、それは単にPromise.allの反対だと思っていたからです。興味深いけれど、ニッチなものだと。しかし、それは間違いでした。これは、以前はぎこちない回避策が必要だった現実世界の問題群に対する、最もクリーンな解決策です。
JavaScriptには4つのPromiseコンビネーターがあります。これらはファミリーとして機能します。個別に学ぶよりも、まとめて理解する方が早いです。
| コンビネーター | 解決する条件 | 拒否する条件 |
|---|---|---|
Promise.all | すべてのPromiseが解決する | いずれかのPromiseが拒否する |
Promise.allSettled | すべてのPromiseが解決する(常に) | 決して拒否しない - 常に解決する |
Promise.race | いずれかのPromiseが解決する | いずれかのPromiseが拒否する |
Promise.any | いずれかのPromiseが解決する | すべてのPromiseが拒否する |
Promise.anyは空の象限に位置します。最初の成功で解決し、失敗をショートサーキットするのではなく、集約します。それがこの機能の全てです。
any + settledはそれぞれrace + allのように振る舞います。any = 最初の勝利、settled = 全員を待つ。これら4つの名前を覚える代わりに、この2つの軸を組み合わせて考えることができます。
「フォールバック」の動作が必要なときに、私が以前書いていたコードは次のとおりです。
| 古いパターン | 実際に必要だったもの | |
|---|---|---|
| Aを試行し、失敗したらBを試行し、失敗したらCを試行する | 最初の成功で解決 - 逐次的な待機なし、帯域幅の無駄なし | 最初の成功にはPromise.anyを使用する |
| どのフェッチが解決したかを確認し、残りを無視する | AggregateErrorは、その詳細を無料で正確に提供する | どのPromiseが最初に解決したかを手動で検査する必要がある |
| タイムアウトまたはフォールバックのための`Promise.race` | `Promise.any` - 最初の成功、正直な失敗報告 | Raceは成功ではなく解決で勝利する - 失敗を静かに飲み込む |
古いアプローチでは、最速のサービスを呼び出し、それが良好であればその結果を使用し、そうでなければフォールバックしていました。Promise.raceは魅力的ですが、競合(race)で静かに失敗します。勝者が成功したのか、単に最初に反応しただけなのかがわかりません。Promise.anyはこれを直接解決します。
const urls = [
"https://api.primary.com/data",
"https://api.backup-1.com/data",
"https://api.backup-2.com/data",
];
try {
const response = await Promise.any(
urls.map((url) => fetch(url).then((r) => r.json()))
);
console.log(
"Got data from first successful fetch:",
response
);
} catch (aggregateError) {
// All three failed - see exactly why
console.log(
"All endpoints failed:",
aggregateError.errors
);
aggregateError.errors.forEach((err, i) => {
console.log(`Source ${i}:`, err.message);
});
}
重要な詳細:Promise.anyが拒否された場合、エラーは通常のErrorではなく、AggregateErrorです。これは、入力イテラブルと同じ順序で、失敗した入力ごとに1つのエントリを持つ.errors配列を保持します。
1. フォールバックを伴う並列機能フラグフェッチ。 3つのフラグプロバイダーを同時に呼び出します。最初に応答したプロバイダーの結果が必要で、すべてのプロバイダーがダウンした場合にのみ失敗を気にします。
2. raceセマンティクスなしのタイムアウトラッパー。 遅い呼び出しをタイムアウトでラップします。
async function fetchWithTimeout(url, ms) {
return Promise.any([
fetch(url),
new Promise((_, reject) =>
setTimeout(
() => reject(new Error("Timeout")),
ms
)
),
]);
}
これはPromise.raceよりもクリーンです。なぜなら、タイムアウトは拒否のみを消費し、解決は消費しないからです。
3. プログレッシブ画像読み込み。 ブラウザがサポートする最初の形式で解決します。
const sources = [
fetchImage("webp"),
fetchImage("avif"),
fetchImage("png"), // fallback
];
const image = await Promise.any(sources); // Use whichever format loaded first
4. 堅牢な認証トークン更新。 2つのプロバイダーから同時に更新し、成功した方を使用します。
const tokens = await Promise.any([
refreshGitHubToken(),
refreshGitLabToken(),
]); // Use tokens regardless of which provider responded
AggregateErrorオブジェクトは、単なる拒否以上のものを明らかにします。
try {
await Promise.any(failingPromises);
} catch (err) {
// err is an AggregateError
console.log(err.name); // "AggregateError"
console.log(err.message); // "All promises were rejected"
console.log(err.errors); // [TypeError, ReferenceError, RangeError]
...
}
err.errorsは個々のエラーオブジェクトを保持するため、ソースごとにロジックを分岐させることができます。これにより、Promise.anyは「最初の成功」のセマンティクスだけでなく、ロギングやエラー報告に真に役立つものとなります。
I/Oを並列で実行することでレイテンシを平坦化することは、JavaScriptの最適化の最も古いトリックです。Promise.allは「すべて成功する必要がある」ケースをカバーしました。Promise.anyは「少なくとも1つが成功する」ケースをカバーします。これはほとんどの現実世界の要求に当てはまります。
比較:
Promise.anyを使用して2つのサービスに並列で発行される同じリクエストは、両方のうち最速のものによって時間が制限されます。遅い失敗コストは、より速いタイムアウトに制限されます。この非対称性は、個々のタイムアウトが長いモバイルや高レイテンシ接続で最も重要になります。
AggregateError.messageは常にリテラル文字列"All promises were rejected"です。デバッグのためにこれを解析せず、代わりに.errorsを使用してください。Promise.anyは解決した場合にのみ.thenの呼び出しを停止するため、別の入力がすでに解決した後でも、拒否する入力は拒否されます。追加の拒否は、キャッチされない限り、未処理の拒否警告を生成します。Promise.any([])はAggregateErrorで拒否され、その.errors配列は空です。これは、Promise.all([])が[]で解決するのとは異なります。// Basic: first success wins
const winner = await Promise.any([
fetch(urlA),
fetch(urlB),
fetch(urlC),
]);
// Fallback: value from whichever call resolved first
// All fail: catch AggregateError - inspect .errors
// Timeout pattern
async function withTimeout(promise, ms) {
return Promise.any([
promise,
new Promise((_, reject) =>
setTimeout(
() => reject(
new Error(`Timed out after ${ms}ms`)
),
ms
)
),
]);
}
// Typed (TypeScript)
function firstSuccessful<T>(
promises: Array<Promise<T>>
): Promise<T> {
return Promise.any(promises);
}
あなたは、地理的に分散された複数のデータセンターのいずれかからユーザーデータをフェッチし、レイテンシを最小限に抑える必要がある重要なマイクロサービスを構築しています。あるデータセンターが遅い、または失敗した場合、次に利用可能な成功したレスポンスのデータをすぐに使用したいと考えています。このシナリオに最も適したPromise combinatorは何ですか、そしてその理由は何ですか?
AI CLIログの整形、Cron式の作成、JWTの解析、chmod権限の計算をオフラインで実行。

ReactForgetとReactコンパイラの内部動作をAST変換、データフロー解析、そしてuseMemoを削除できる理由を理解することで詳細に解説します。
続きを読む
Next.js14でServer Actions、useFormState、useFormStatus、Zodを使ったフォーム管理を詳しく解説し、クライアントサイドJavaScriptの負担なく、モダンでプログレッシブエンハンスメントなフォームを構築する方法を学びましょう。
続きを読む
TypeScript 5.8+ Stage 3 Decoratorsの包括的なガイドで、本番環境レベルのアーキテクチャとコード例を用いて、本番環境でのメタプログラミング、DI、およびruntimeバリデーションについて解説します。
続きを読む