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

JavaScript JavaScript
スポンサーリンク

Promise.all を一言でいうと

Promise.all は、
「複数の Promise を“同時に走らせて”、全部そろったら結果をまとめて受け取るための関数」
です。

たとえば、

ユーザー情報を取得する
投稿一覧を取得する
通知一覧を取得する

この 3 つを「順番に」ではなく「同時に」投げて、
「全部終わったタイミングでまとめて処理したい」 ときに使います。

ここが重要です。
Promise.all は「全部終わるのを待つ道具」+「結果を配列でまとめて渡してくれる道具」です。
そして同時に、「1つでも失敗したら全体をエラー扱いにする」 という性質も持っています。


基本の形と動き方

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

基本の形はこうです。

Promise.all([promise1, promise2, promise3])
  .then((results) => {
    // 全部の Promise が「成功」したときに呼ばれる
  })
  .catch((error) => {
    // 1つでも「失敗」したらここ
  });
JavaScript

ポイントは 2 つです。

1つ目に、
Promise の配列を渡す こと。
(中身は Promise であれば OK。fetch の返り値など。)

2つ目に、
全部が成功した場合、thenresults という 配列 で結果が渡ってくること。
順番は「配列の順番」と一致します。

いちばんシンプルな例

const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);

Promise.all([p1, p2, p3]).then((results) => {
  console.log(results); // [1, 2, 3]
});
JavaScript

ここで起きていることは:

p1, p2, p3 はすでに成功済みの Promise
Promise.all は「全部成功している」と判断
thenresults[1, 2, 3] が渡る

「配列の 0 番目に p1 の結果、1 番目に p2 の結果、2 番目に p3 の結果」という対応になっています。


実用的な例:複数の API を同時に呼ぶ

ユーザー・投稿・通知をまとめて取得する

それぞれ Promise を返す関数があるとします。

function fetchUser() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("ユーザー取得完了");
      resolve({ id: 1, name: "Taro" });
    }, 1000);
  });
}

function fetchPosts() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("投稿取得完了");
      resolve(["投稿1", "投稿2"]);
    }, 1500);
  });
}

function fetchNotifications() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("通知取得完了");
      resolve(["通知A", "通知B"]);
    }, 500);
  });
}
JavaScript

これを Promise.all で一気に呼びます。

Promise.all([fetchUser(), fetchPosts(), fetchNotifications()])
  .then(([user, posts, notifications]) => {
    console.log("ユーザー:", user);
    console.log("投稿:", posts);
    console.log("通知:", notifications);
  })
  .catch((err) => {
    console.error("どれかの取得に失敗:", err);
  });
JavaScript

動きとしてはこうです。

fetchUser() 開始(1秒)
fetchPosts() 開始(1.5秒)
fetchNotifications() 開始(0.5秒)

3つとも「同時に走り始める」。
1番速いもの・遅いものが混ざっていても、
一番遅いものが終わったタイミングで then が呼ばれ、全部の結果が配列で渡される

ここが重要です。
Promise.all は「順番にやる」のではなく、「同時に走らせて、最後にそろえる」ための仕組み
だとイメージしてください。


1つでも失敗したらどうなるか(エラーの性質)

「オールオアナッシング」(全成功か、どこかで失敗か)

Promise.all のエラー処理は、とてもシンプルです。

渡した Promise のうち 1つでも rejected になると、Promise.all 全体が rejected になる

例を見てみます。

const ok1 = Promise.resolve("OK1");

const fail = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(new Error("失敗した Promise"));
  }, 1000);
});

const ok2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve("OK2");
  }, 1500);
});

Promise.all([ok1, fail, ok2])
  .then((results) => {
    console.log("成功:", results);
  })
  .catch((err) => {
    console.error("catch:", err.message);
  });
JavaScript

この場合の流れは:

ok1 → 即成功
fail → 1秒後に失敗
ok2 → 1.5秒後に成功

ですが、Promise.all は、

fail が失敗した瞬間、「全体として rejected」と決定
→ すぐに catch が呼ばれる
→ ok2 の結果は待たない / 使われない

という動きをします。

出力は:

catch: 失敗した Promise

のみです。

ここが重要です。
Promise.all は「1つでもダメなら全体としてダメ」という性質。
「全部揃った結果」が欲しいときに使う道具なので、部分的な成功は扱いません。

「どれが失敗したか」の情報

catch に渡されるのは、「最初に失敗した Promise のエラー」です。

上の例では fail が最初に失敗したので、その Error が err になります。

「どの Promise が落ちたか」も知りたければ、
それぞれの reject に少し情報を乗せるのもよくあるやり方です。

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

Promise.all([p1, p2]).catch((err) => {
  console.error(err.message); // "p1 が失敗"
});
JavaScript

結果の配列の順番について

「完了順」ではなく「配列の順番」

ここは初心者が混乱しがちなポイントなので、はっきりさせておきます。

Promise.all に渡した配列が [p1, p2, p3] なら、
thenresults も常に [p1 の結果, p2 の結果, p3 の結果]
この順番になります。

「どの Promise が先に終わったか」は関係ありません。

例:

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

const fast = new Promise((resolve) => {
  setTimeout(() => resolve("fast"), 100);
});

Promise.all([slow, fast]).then((results) => {
  console.log(results); // ["slow", "fast"]
});
JavaScript

fast のほうが 900ms も早く終わりますが、
results[0] は slow の結果
results[1] は fast の結果
です。

ここが重要です。
Promise.all の結果の配列は「完了順」ではなく、「渡したときのインデックス順」
このおかげで「どの値が何に対応しているか」が分かりやすくなっています。


よくある使い方パターン

配列分割代入で名前をつけて受け取る

配列のままだと results[0] などが分かりづらいので、
分割代入で受け取るのがおすすめです。

Promise.all([fetchUser(), fetchPosts(), fetchNotifications()])
  .then(([user, posts, notifications]) => {
    console.log(user);
    console.log(posts);
    console.log(notifications);
  });
JavaScript

[user, posts, notifications] という形にすれば、
「どれがどの結果か」が一目で分かります。

「普通の値」と Promise が混ざっていてもよい

Promise.all に渡す配列の中身は、
Promise でなくても OK です。
普通の値は「即座に成功した Promise」として扱われます。

Promise.all([
  Promise.resolve(1),
  2,                     // これも OK(そのまま 2 になる)
  new Promise((resolve) => setTimeout(() => resolve(3), 500)),
]).then((results) => {
  console.log(results); // [1, 2, 3]
});
JavaScript

使い始めのうちは、「全部 Promise にしておく」方が分かりやすいですが、
こういう柔軟性もある、くらいで覚えておくと良いです。


初心者としての「Promise.all」の押さえどころ

最後に、本当に大事なポイントだけを整理します。

Promise.all([p1, p2, p3]) は、「p1, p2, p3 を同時に走らせて、“全部成功したら” まとめて結果を配列で返す」ための関数。

全部成功したとき → then に [p1 の結果, p2 の結果, p3 の結果] が渡る(順番は配列の順番)。

1つでも失敗したとき → その時点で全体が rejected になり、catch に「最初に失敗した Promise のエラー」が渡る。

「全部揃ってから使いたいデータ」(ユーザー+投稿+通知など)を取るときにとても有効。

結果の配列は完了順ではなく、渡したインデックス順。
混乱しないように、then(([a,b,c]) => ...) のように分割代入で名前をつけて受け取ると読みやすい。

ここが重要です。
Promise.all は「順番に遅くする」のではなく、「同時に投げて待ち時間を短くし、結果だけ最後にそろえる」ための武器です。

練習としては、

setTimeout を使って

  • 500ms 後に “A”
  • 1000ms 後に “B”
  • 200ms 後に “C”

を resolve する Promise を 3 つ作り、
Promise.all[a, b, c] として受け取ってログを出してみてください。

そのとき、
「C が一番早く終わったのに、配列の順番は [A, B, C] なんだ」
という感覚を自分の目で確かめると、
Promise.all の挙動がかなりしっかりと腹に落ちるはずです。

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