Promise.any()は、複数の非同期処理のうち「最初に成功したもの」だけを受け取るメソッドです。全部失敗したときはAggregateErrorを投げます。ES2021で標準化され、現在は全モダンブラウザと Node.js 15 以降で使えます。
使いどころははっきりしていて、ミラーサーバーやCDNのフォールバックです。逆に、true/false を返す判定処理には使えません(後述しますが、これは非常によくある誤用です)。この記事では、正しい使い方と、all / race / allSettled との使い分けを整理します。
スポンサーリンク
Promise.any() の基本動作
最初に fulfilled になった Promise の値を返し、それ以外は無視します。 失敗(rejected)は、全部が失敗するまで結果に影響しません。
const p1 = Promise.reject(new Error('エラー1'));
const p2 = new Promise((resolve) => setTimeout(resolve, 100, '成功2'));
const p3 = new Promise((resolve) => setTimeout(resolve, 500, '成功3'));
const result = await Promise.any([p1, p2, p3]);
console.log(result); // '成功2'
p1は即座に失敗しますが、Promise.any()は待ち続け、100ms後に成功したp2の値を返します。p3の結果は捨てられます(処理自体は止まりません。これは後述します)。
全て失敗したとき:AggregateError
全ての Promise が rejected になると、AggregateErrorが投げられます。errorsプロパティに、元の失敗理由が配列で入っているのが特徴です。
try {
await Promise.any([
Promise.reject(new Error('エラー1')),
Promise.reject(new Error('エラー2')),
Promise.reject(new Error('エラー3')),
]);
} catch (err) {
if (err instanceof AggregateError) {
console.error('すべて失敗しました');
err.errors.forEach((e) => console.error(' -', e.message));
// - エラー1
// - エラー2
// - エラー3
}
}
空の配列を渡した場合も即座にAggregateErrorで reject されます。 動的に配列を組み立てるコードでは、この分岐を忘れると原因不明のエラーになります。
const sources = getSources(); // 条件によっては空になる
if (sources.length === 0) {
throw new Error('取得先が1つもありません');
}
const data = await Promise.any(sources.map(fetchFrom));
スポンサーリンク
正しい使いどころ:フォールバック
同じデータを複数の場所から取れるときが本来の用途です。どこか1つでも応答すればよく、しかも一番速いものを使いたい、という状況にぴったり合います。
async function fetchJson(url, signal) {
const res = await fetch(url, { signal });
if (!res.ok) throw new Error(`HTTP ${res.status} (${url})`);
return res.json();
}
const MIRRORS = [
'https://cdn1.example.com/data.json',
'https://cdn2.example.com/data.json',
'https://origin.example.com/data.json',
];
try {
const data = await Promise.any(MIRRORS.map((u) => fetchJson(u)));
render(data);
} catch (err) {
// AggregateError。全ミラーがダウンしている
console.error('全ての取得先が失敗:', err.errors);
showMessage('データを取得できませんでした');
}
ここで「HTTPエラーを例外にする」処理が重要です。fetch()は404や500でも Promise を fulfilled にするため、res.okを確認しないと「404のレスポンスが最速だったのでそれを採用する」という結果になります。
やってはいけない使い方:判定処理
ネット上のサンプルでよく見かける、次のようなコードは意図通りに動きません。
// ❌ 動かない例
const validateEmail = (v) => Promise.resolve(/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v));
const validatePhone = (v) => Promise.resolve(/^\d{10}$/.test(v));
Promise.any([
validateEmail('これはメールではない'),
validatePhone('あいうえお'),
]).then((r) => console.log('どれかに合格:', r));
このコードは「どれかに合格: false」と出力します。理由は単純で、Promise.resolve(false)は「falseという値で成功したPromise」だからです。Promise.any()が見ているのは値の中身ではなく、fulfilled か rejected かだけです。
判定に使いたいなら、失敗を reject で表現する必要があります。
// ✅ 失敗を reject にする
const validateEmail = async (v) => {
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)) throw new Error('メール形式ではありません');
return { type: 'email', value: v };
};
const validatePhone = async (v) => {
if (!/^\d{10}$/.test(v)) throw new Error('電話番号形式ではありません');
return { type: 'phone', value: v };
};
try {
const matched = await Promise.any([validateEmail(input), validatePhone(input)]);
console.log('形式:', matched.type);
} catch (err) {
console.log('どの形式にも当てはまりません');
}
そもそも同期的に判定できる処理を Promise でくるむ意味はありません。上の例は書き方の説明用であって、実務ではArray.prototype.some()で十分です。Promise.any()を使う価値があるのは、本当に時間のかかる処理が並列に走る場面だけです。
スポンサーリンク
負けた処理は止まらない
見落としやすい点として、Promise.any()が決着しても、残りの処理は動き続けます。Promiseには中断の仕組みが無いためです。
通信であれば、AbortControllerで明示的に止める必要があります。
const controller = new AbortController();
try {
const data = await Promise.any(
MIRRORS.map((u) => fetchJson(u, controller.signal))
);
render(data);
} finally {
// 勝者が決まったら残りの通信を打ち切る
controller.abort();
}
finallyに置くのがポイントです。成功時も失敗時も、余計なリクエストを残しません。これを書かないと、ミラーの数だけ通信が最後まで走り続け、通信量と回線を無駄に消費します。
all / allSettled / race / any の使い分け
| メソッド | 決着する条件 | 返る値 | 使う場面 |
|---|---|---|---|
Promise.all() |
全部成功/1つでも失敗 | 値の配列 | 全部そろわないと画面が作れない |
Promise.allSettled() |
全部決着 | 状態つきの配列 | 一部失敗しても表示を続けたい |
Promise.race() |
最初に決着した1つ(成功・失敗問わず) | その値/理由 | タイムアウトとの競争 |
Promise.any() |
最初に成功した1つ | その値 | フォールバック、ミラー |
race()とany()の違いが最も間違えやすいところです。
const fast = Promise.reject(new Error('速いが失敗'));
const slow = new Promise((r) => setTimeout(r, 100, '遅いが成功'));
await Promise.race([fast, slow]); // ❌ reject される(速い方が失敗だから)
await Promise.any([fast, slow]); // ✅ '遅いが成功' が返る
「速さ」で決めるのがrace()、「成功」で決めるのがany()と覚えると混乱しません。フォールバックの用途でrace()を使うと、一番速く落ちたサーバーのエラーで全体が失敗します。
対応状況
| 環境 | 対応バージョン |
|---|---|
| Chrome / Edge | 85 |
| Firefox | 79 |
| Safari | 14 |
| Node.js | 15 |
ES2021の一部として標準化されており、ポリフィルを入れる必要はもうありません。
まとめ
Promise.any()は最初に成功したPromiseの値を返す。全部失敗ならAggregateErrorerr.errorsに全ての失敗理由が配列で入る。空配列を渡した場合も即 reject- 本来の用途はミラー・CDNのフォールバック
Promise.resolve(false)は「成功」扱い。 判定に使うなら失敗をthrowで表す- 決着しても負けた処理は止まらない。
AbortControllerでfinallyから中断する race()は「速さ」、any()は「成功」で決まるfetchと併用するときはres.okを必ず確認する(fetch() APIの使い方)
「どれか1つ返ってくればいい」という要件は意外と多く見つかります。tryを何段も重ねたフォールバック処理を書いているなら、Promise.any()で平らにできないか検討してみてください。