JavaScript 逆引き集 | Promise.race(最初)

JavaScript JavaScript
スポンサーリンク

Promise.race の基本と実践(最初に決着したひとつ)

Promise.race は「複数の非同期のうち、もっとも早く“決着”したひとつの結果(成功または失敗)で返す」ための静的メソッドです。配列などのイテラブルを受け取り、最初に完了した Promise の最終状態に従って自身を解決または拒否します。


基本の書き方と挙動

const p1 = new Promise(res => setTimeout(() => res("A"), 300));
const p2 = new Promise(res => setTimeout(() => res("B"), 200));

const fastest = await Promise.race([p1, p2]);
console.log(fastest); // "B"
JavaScript
  • 最初に決着: 最初に fulfill されたらその値で resolve、最初に reject されたらその理由で rejectr
  • イテラブル入力: 配列など反復可能なオブジェクトを渡せる。空を渡すと永遠に待機状態の Promise になる。
  • 順序ではなく速度: 返るのは最速の結果。渡した並び順は関係ない。

直感で掴む(例で理解)

const slow = new Promise(res => setTimeout(res, 500, "slow"));
const fast = new Promise(res => setTimeout(res, 100, "fast"));

console.log(await Promise.race([slow, fast])); // "fast"
JavaScript
  • ポイント: 最速が勝ち。遅い方はその後どうなっても関係ない(勝敗決定済み)。

すぐ使えるテンプレート集

1) タイムアウト付きラッパー

function withTimeout(promise, ms) {
  const timeout = new Promise((_, rej) => setTimeout(() => rej(new Error("Timeout")), ms));
  return Promise.race([promise, timeout]);
}

// 使用例
const slowOp = new Promise(res => setTimeout(() => res("OK"), 1000));
try {
  const r = await withTimeout(slowOp, 300);
  console.log(r);
} catch (e) {
  console.log(e.message); // "Timeout"
}
JavaScript
  • ポイント: 実処理とタイマーを競わせ、時間超過ならタイムアウトを優先。

2) 複数エンドポイントの「最速採用」

async function fetchFast(urls) {
  const reqs = urls.map(u => fetch(u));
  return Promise.race(reqs); // 最初に返ってきたレスポンスだけ使う
}
JavaScript
  • ポイント: ミラーやCDNに同時に投げて、最初のレスポンスを採用する戦略。

3) 最初に成功したものだけ欲しいなら Promise.any

// race は最初の「失敗」でも止まる。成功限定で最速が欲しいなら any。
const result = await Promise.any([
  Promise.reject(new Error("fail")),
  Promise.resolve("ok"),
]);
console.log(result); // "ok"
JavaScript
  • ポイント: race は成功・失敗のどちらでも「最初」に従う。成功だけの最速なら any が適任。

実務でのパターンとコツ

  • タイムアウト管理: ネットワークやI/Oが発生する場面では race で締め切りを設けるのが定石。
  • フェイルファスト: 最初の失敗をすぐに検出したいときは race に「失敗要因」を混ぜて早期中断。
  • 成功限定の最速: 「失敗は無視して、初めて成功したもの」を採用したい場面は Promise.any を使い分け。
  • 全件の完了が必要なら all/allSettled: race は「最初だけ」。全部終わってから判断したいなら all や allSettled を使う。

よくある落とし穴と対策

  • 最初の失敗で落ちる誤解: race は最初の決着が失敗ならそこで reject。成功限定ではない。
    • 対策: 成功限定は Promise.any、または失敗を握りつぶすラップを使う。
  • 空のイテラブルを渡す: 永久に pending の Promise を返す。
    • 対策: 入力チェックして最低1件は渡す。
  • 後続の副作用が残る: 最初に決着した後も、他の Promise は裏で進み続ける。
    • 対策: 必要なら中断可能なAPI(AbortController等)と併用してキャンセル設計を入れる。
  • 順序の勘違い: 並び順ではなく速度で決まる。
    • 対策: 「最速を採用する」前提で設計する。

練習問題(手を動かして覚える)

// 1) 最速が勝つ
const d = ms => new Promise(res => setTimeout(() => res(ms), ms));
console.log(await Promise.race([d(300), d(150), d(200)])); // 150

// 2) 最初が失敗したら即エラー
const ok = d(200);
const ng = new Promise((_, rej) => setTimeout(() => rej(new Error("fail")), 100));
try {
  await Promise.race([ok, ng]);
} catch (e) {
  console.log("catch:", e.message); // "fail"
}

// 3) タイムアウトラッパー
const slow = d(500);
try {
  const r = await withTimeout(slow, 200);
  console.log(r);
} catch (e) {
  console.log("timeout:", e.message);
}

// 4) 成功限定の最速(any)
const out = await Promise.any([
  Promise.reject(new Error("x")),
  Promise.resolve("y"),
  d(50).then(() => "z"),
]);
console.log(out); // "y"(or "z" の最速)
JavaScript

直感的な指針

  • 「最初に決着したひとつ」で次へ進めたいなら race。
  • タイムアウト・ミラー競合・フェイルファストに有効。
  • 成功限定は any、全完了は all/allSettled と使い分け。
タイトルとURLをコピーしました