JavaScript 逆引き集 | Promise.all(並列)

JavaScript JavaScript
スポンサーリンク

Promise.all の基本と実践(並列処理)

複数の非同期処理を「同時に走らせて、全部そろったら結果を受け取りたい」—その最短ルートが Promise.all。依存関係がないタスクをまとめて走らせると、全体時間を短縮できます。


基本の書き方と挙動

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

const [a, b] = await Promise.all([p1, p2]);
console.log(a, b); // "A" "B"
JavaScript
  • 成功時: 全ての Promise が成功したら、同じ並びで結果の配列が返る。
  • 失敗時: ひとつでも失敗したら、即座に例外(reject)として投げられる。
  • 順番: 完了順ではなく、渡した配列の順番で結果が並ぶ。

直列 vs 並列(時間の違いを体感)

const task = ms => new Promise(res => setTimeout(() => res(ms), ms));

// 直列(逐次に待つ → 合計時間)
const a = await task(300);
const b = await task(200);
// 合計 約500ms

// 並列(同時に開始 → 最大の時間に収束)
const [x, y] = await Promise.all([task(300), task(200)]);
// 約300ms
JavaScript
  • 指針: 依存がないなら並列。依存があるなら直列。

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

API 2本を並列で取得

async function loadUserAndPosts(userId) {
  const [user, posts] = await Promise.all([
    fetch(`/api/users/${userId}`).then(r => r.json()),
    fetch(`/api/users/${userId}/posts`).then(r => r.json()),
  ]);
  return { user, posts };
}
JavaScript

エラーをまとめてハンドリング

async function loadAll() {
  try {
    const [config, profile, stats] = await Promise.all([
      fetch("/config").then(r => r.json()),
      fetch("/profile").then(r => r.json()),
      fetch("/stats").then(r => r.json()),
    ]);
    console.log({ config, profile, stats });
  } catch (e) {
    console.error("どれかが失敗:", e.message);
  }
}
JavaScript

値配列を返す関数群を並列実行

const fns = [
  async () => 1,
  async () => 2,
  async () => 3,
];

const results = await Promise.all(fns.map(fn => fn()));
console.log(results); // [1, 2, 3]
JavaScript

マップでリクエストを構築して順序保持

const endpoints = ["users", "posts", "comments"];
const promises = endpoints.map(ep => fetch(`/api/${ep}`).then(r => r.json()));
const [users, posts, comments] = await Promise.all(promises);
JavaScript

実務でのパターンとコツ

  • 依存なしは並列: 先に Promise を作ってから Promise.all でまとめて待つ。
  • 結果の型整合性: 返り値の並びが重要なら、入力配列の順番を明確に管理。
  • 部分失敗の扱い: 1つでも失敗したら全体が例外になる。部分的に成功・失敗を区別したいなら Promise.allSettled を検討。
  • 同時接続の制御: 大量の並列は負荷が高い。必要なら「同時数を絞る」並列キューを用意。
// 同時N件に制限する簡易並列制御
async function limitAll(tasks, limit = 3) {
  const results = [];
  let i = 0;
  const runners = Array.from({ length: limit }, async () => {
    while (i < tasks.length) {
      const idx = i++;
      results[idx] = await tasks[idx]();
    }
  });
  await Promise.all(runners);
  return results;
}
JavaScript

よくある落とし穴と対策

  • await を都度置いて直列にしてしまう: 先に配列で「開始」してから Promise.all で待つ。
    • 対策: const pA = taskA(); const pB = taskB(); const [a, b] = await Promise.all([pA, pB]);
  • ひとつの失敗で全体が止まる: 失敗を許容したいなら各 Promise を個別に try/catch するか、allSettled を検討。
    • 対策: Promise.allSettled([...]) で結果を集計し、成功だけを取り出す。
  • 長い配列でのエラー源特定が難しい: 入力配列の要素に識別情報を持たせる、ログを埋める。
    • 対策: ラップしてコンテキスト付きでエラーを投げる。
  • 過剰な並列でサーバー負荷増大: バッチに分ける、同時数を制限する。
    • 対策: 上記の limitAll のような並列制御を使う。

比較(必要なら使い分け)

  • Promise.all: 全て成功で結果配列。ひとつでも失敗で例外。
  • Promise.allSettled: 全て完了後に成功/失敗ステータスを返す。部分成功を扱いやすい。
  • Promise.race: 最初に完了したひとつの結果で決着。
  • Promise.any: 最初に成功した結果で決着(全部失敗なら例外)。

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

// 1) 3つの遅延を並列で待つ
const d = ms => new Promise(res => setTimeout(() => res(ms), ms));
console.time("parallel");
const out = await Promise.all([d(200), d(300), d(150)]);
console.timeEnd("parallel");
console.log(out); // [200, 300, 150]

// 2) 1つ失敗した場合の挙動
const ok = d(100);
const ng = new Promise((_, rej) => setTimeout(() => rej(new Error("fail")), 120));
try {
  await Promise.all([ok, ng]);
} catch (e) {
  console.log("catch:", e.message); // "fail"
}

// 3) allSettledで部分成功を処理
const results = await Promise.allSettled([
  Promise.resolve(1),
  Promise.reject(new Error("x")),
  Promise.resolve(3),
]);
const successValues = results
  .filter(r => r.status === "fulfilled")
  .map(r => r.value);
console.log(successValues); // [1, 3]

// 4) 同時数制限(5件を2並列で処理)
const tasks = Array.from({ length: 5 }, (_, i) => () => d(100 + i * 20));
console.time("limit");
const res = await limitAll(tasks, 2);
console.timeEnd("limit");
console.log(res);
JavaScript

直感的な指針

  • 依存がないなら並列化して Promise.all。
  • 「全部そろったら次へ」—失敗はひとつでも即エラー。
  • 部分成功が欲しいなら allSettled。負荷が気になるなら並列数を絞る。
  • 順序は入力配列どおり。開始は先に、待つのはまとめて。
タイトルとURLをコピーしました