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。負荷が気になるなら並列数を絞る。
- 順序は入力配列どおり。開始は先に、待つのはまとめて。
