JavaScript | 非同期処理:Promise 応用 – Promise.any

JavaScript JavaScript
スポンサーリンク

Promise.any を一言でいうと

Promise.any は、
「複数の Promise のうち、“最初に成功したもの 1 つ” だけの結果を採用する関数」 です。

race が「一番早く 成功でも失敗でも 決着したやつの結果」を採用するのに対して、
any「失敗は無視して、とにかく最初に成功したやつを採用する」 という性格を持っています。

ここが重要です。
Promise.race = 「最初に決着したやつ(成功 or 失敗)」
Promise.any = 「最初に成功したやつ。全員失敗したらまとめてエラー」

とイメージすると区別しやすくなります。


Promise.any の基本的な動き

どう呼ぶか(シグネチャ)

形は他の Promise 系関数と同じです。

Promise.any([promise1, promise2, promise3])
  .then((value) => {
    // 最初に「成功」した Promise の値
  })
  .catch((error) => {
    // 全部「失敗」した場合だけここに来る
  });
JavaScript

挙動をまとめると、

複数の Promise を一斉にスタート
成功したものが出てくるまで待つ
最初に成功した Promise の値を then に渡す
もし全ての Promise が失敗したら、catch に「複数失敗をまとめたエラー」が渡る

という流れになります。

いちばんシンプルな例

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("p1 失敗")), 300);
});

const p2 = new Promise((resolve) => {
  setTimeout(() => resolve("p2 成功"), 500);
});

const p3 = new Promise((resolve) => {
  setTimeout(() => resolve("p3 成功"), 800);
});

Promise.any([p1, p2, p3])
  .then((value) => {
    console.log("最初に成功した値:", value);  // "p2 成功"
  })
  .catch((err) => {
    console.error("全部失敗:", err);
  });
JavaScript

流れを言葉で追うと、

p1 → 300ms 後に失敗
p2 → 500ms 後に成功
p3 → 800ms 後に成功

any は「失敗はスルーして良い、最初に成功したやつが欲しい」ので、
300ms の p1 は無視され、
500ms の p2 が「最初の成功」として採用されます。
p3 は、その後で成功しますが、すでに any の結果は決まっているので使われません。

ここが重要です。
Promise.any は「失敗を弾いて、最初に成功したやつだけを取るフィルター」
とイメージしておくとよいです。


Promise.race / Promise.all と比べてみる

race との違い(失敗が勝者になるかどうか)

Promise.race の場合は、「最初に成功 or 失敗したやつ」が勝ちです。

const ok = new Promise((resolve) => {
  setTimeout(() => resolve("OK"), 1000);
});

const failFast = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("速いエラー")), 300);
});

Promise.race([ok, failFast])
  .then((v) => console.log("race 成功:", v))
  .catch((e) => console.log("race 失敗:", e.message));
JavaScript

この例では、300ms の failFast が「最初に決着」するので、
race 全体は reject(失敗)になり、catch に来ます。

これを any に変えるとどうなるか。

Promise.any([ok, failFast])
  .then((v) => console.log("any 成功:", v))
  .catch((e) => console.log("any 失敗:", e));
JavaScript

流れはこうです。

300ms:failFast が失敗 → any は「失敗なのでスルー」
1000ms:ok が成功 → any が「これが最初の成功!」として採用、then に “OK” が渡る

つまり同じ 2 つの Promise でも、

race → 速い失敗が勝って、全体がエラー
any → 失敗は無視して、後から来た成功を採用

という違いが出ます。

all と any の方向性の違い

Promise.all は、「全部成功してくれないと困る」状況向きです。

全部成功 → then に [全部の結果]
1つでも失敗 → 即 catch

Promise.any は、「全部成功してほしいわけじゃない、どれか 1 つでも成功してくれればいい」状況向きです。

1つでも成功 → その最初の成功を then へ
全部失敗 → catch

ここが重要です。
all = 「全員合格してほしい試験」
any = 「誰か 1 人でも合格者が出れば OK な試験」
race = 「誰でもいいから最初にゴールした人を採用」

というイメージで覚えておくと、使い分けが直感的になります。


全部失敗したときのエラー(AggregateError)

1つでも成功すれば then、全員失敗なら catch

Promise.any が catch に来るのは、
「配列に渡した Promise が、全部 reject されたときだけ」 です。

const p1 = Promise.reject(new Error("p1 失敗"));
const p2 = Promise.reject(new Error("p2 失敗"));

Promise.any([p1, p2])
  .then((value) => {
    console.log("成功:", value); // ここには来ない
  })
  .catch((err) => {
    console.log("全部失敗したときのエラー:", err);
  });
JavaScript

ここで err として渡ってくるのは AggregateError という特殊なエラーオブジェクトです。

ざっくり言うと、
「中に複数のエラーをまとめて持てるエラー」です。

AggregateError の中身をサッと見る

ブラウザや環境によりますが、だいたいこんなイメージです。

Promise.any([
  Promise.reject(new Error("A")),
  Promise.reject(new Error("B")),
]).catch((err) => {
  console.log(err instanceof AggregateError); // true のことが多い

  console.log(err.errors); // [Error("A"), Error("B")] のような配列
});
JavaScript

細かい仕様を全部覚える必要はありませんが、

全部失敗した場合は、「複数の失敗理由」を中に持ったエラーが来る

ということだけ知っておくと、ログを眺めるときに混乱しにくくなります。

ここが重要です。
Promise.any の catch に来るのは、「本当に誰一人成功しなかった時だけ」。
そのときのエラーは、「みんなの失敗をまとめた AggregateError」になりやすい。


どんな場面で Promise.any を使うと嬉しいか

例1:ミラーサーバー(どれか 1 つから取れればいい)

例えば、

サーバー A にアクセス
サーバー B にも同じデータがある
サーバー C にもある

という「同じ内容を持つミラーサーバー」が複数ある状況を考えてみます。

「どれか 1 つでも返してくれればいい。
一番早く成功したサーバーの結果を使いたい。」

こういうときに Promise.any はピッタリです。

function fetchFromA() {
  return fetch("https://server-a.example.com/data");
}

function fetchFromB() {
  return fetch("https://server-b.example.com/data");
}

function fetchFromC() {
  return fetch("https://server-c.example.com/data");
}

Promise.any([fetchFromA(), fetchFromB(), fetchFromC()])
  .then((response) => {
    console.log("どれかのサーバーが成功:", response.url);
  })
  .catch((err) => {
    console.error("全部ダメだった…", err);
  });
JavaScript

この場合の動きは、

3 つのサーバーに同時にリクエストを投げる
→ 一番早く成功したレスポンスを採用
→ 他は結果としては無視

となります。

1 つ目が落ちても、2 つ目や 3 つ目が成功すれば OK なので、
「部分的な失敗には強いけど、完全なる全滅はエラーにしたい」 という用途に向いています。

例2:メイン手段+予備手段(フォールバック)

もう少し単純化した例として、

メインの処理(ちょっと速いがたまに失敗する)
予備の処理(少し遅いが安定している)

みたいなときにも使えます。

function mainSource() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const ok = Math.random() > 0.5;
      if (ok) resolve("メインの結果");
      else reject(new Error("メイン失敗"));
    }, 200);
  });
}

function backupSource() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("バックアップの結果");
    }, 500);
  });
}

Promise.any([mainSource(), backupSource()])
  .then((value) => {
    console.log("採用された結果:", value);
  })
  .catch((err) => {
    console.error("両方ダメだった:", err);
  });
JavaScript

ここでは、

メインが運良く成功すれば 200ms で終了
メインが失敗しても、バックアップが 500ms で成功すればそれを採用

という、「メイン+フォールバック」の形になります。

ここが重要です。
Promise.any は、「いくつか候補があって、そのうち 1 つでも成功してくれればいい」という場面で真価を発揮します。


ちょっとした注意点とイメージ整理

他の Promise は止まらない(race と同じ)

Promise.anyraceall と同様、
他の Promise の実行を途中で止めるわけではありません。

最初に成功したものを採用したあとも、
裏では残りの Promise が resolve / reject されます。

「本当に途中でキャンセルしたい」場合は、
AbortController や独自のキャンセルロジックが別途必要になる、という点は
Promise.race と同じです。

all / allSettled / race / any のざっくりマップ

ここまでいろいろ出てきたので、感覚的な整理だけしておきます。

全部成功してほしい → Promise.all
全員の結果(成功・失敗)を報告してほしい → Promise.allSettled
とにかく一番早いやつ(成功でも失敗でも) → Promise.race
どれか 1 つでも成功してくれればいい → Promise.any

ここが重要です。
「全員」「全員+状態」「最速」「誰か 1 人成功」の 4 パターンを持っておくと、
非同期処理を設計するときの選択肢が一気に増えます。


初心者向け「Promise.any」の押さえどころ

最後に、最低限ここだけは掴んでおけば OK、というポイントをまとめます。

Promise.any([p1, p2, p3]) は、「p1, p2, p3 のうち、最初に成功した 1 つの結果だけを then に渡す」関数。

成功は最初の 1 回だけ。
最初の成功が決まった時点で、any の結果は確定する(残りは無視)。

1 つでも成功すれば catch には来ない。
catch に来るのは「全員失敗したときだけ」で、そのときは AggregateError 的なエラーになる。

race は「最初に成功 or 失敗したやつ」が勝ち → 早い失敗も勝つ。
any は「成功だけが勝者」、失敗はスルーされる → 全滅したときだけエラー。

ミラーサーバー、メイン+フォールバックなど、「候補が複数あってどれか 1 つでも成功してくれればいい」場面で使うと強い。

ここが重要です。
Promise.any を「失敗をやり過ごして、最初の成功だけを拾ってくる“センサー”」として捉えると、
どんな場面で使えばいいかが自然と見えてきます。

練習としては、

何回かに 1 回だけ成功する Promise
必ず成功する Promise(少し遅い)

を用意して Promise.any に渡し、
何度か実行して「早めの成功」「遅めの成功」「全員失敗」のパターンを試してみると、
any の性格が一気に体に馴染んでくるはずです。

タイトルとURLをコピーしました