複数の非同期処理を同時に走らせる

await をループの中で書くと1つずつ順番に待ってしまいます。Promise.all で同時に走らせる書き方、1つ失敗したときの扱い、forEach で待てない理由、同時数を絞る方法を確かめます。

実行して検証済み処理系Node 22.23.3対応ES2015難易度●●●○○目安 約9分更新

答え

map() で先に全部始めてから、Promise.all() でまとめて待ちます。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
[ 2, 4, 6 ]
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
開始 1 結果 2 開始 2 結果 4 開始 3 結果 6

結果は渡した順に並びます。 終わった順ではありません。

何が違うのか

for の中で await すると、1つ終わってから次を始めます。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
開始 1 開始 2 開始 3 結果 [ 2, 4, 6 ]
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
落ちた: 3 が読めません

「開始」と「結果」が交互に出ています。 前のが終わるまで次が始まっていません。

map() で先に全部始めると、こうなります。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
[ { status: 'fulfilled', value: 1 }, { status: 'rejected', reason: Error: 失敗 } ] [ 1 ] [ '失敗' ]
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
よし race は最初の決着: だめ

「開始」が3つ続けて出ました。 3つとも走り出してから、まとめて待っています。

通信のように待ち時間が長い処理なら、かかる時間が全部の合計からいちばん遅い1つに縮みます。

計算だけの処理は速くなりません。JavaScript は1本の流れで動くので、待ち時間があるものにしか効きません。

1つ失敗すると全部落ちる

Promise.all() は、1つでも失敗した時点で全体が失敗になります。

javascript
const load = async (id) => id * 2;
const out = [];

[1, 2, 3].forEach(async (id) => {
out.push(await load(id));
});

console.log(out);
[]
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
[ 2, 4, 6 ]

成功した分も受け取れません。 全部そろわないと意味がないとき(画面を描くのに3つとも要る、など)は、これが正しい動きです。

失敗しても他を使いたいなら

Promise.allSettled() は、全部の結果を成否つきで返します。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
この回: [ 1, 2 ] この回: [ 3, 4 ] この回: [ 5 ] [ 10, 20, 30, 40, 50 ]

例外は投げられません。 status を見て自分で分けます。

使うもの 動き
Promise.all() 1つ失敗したら全体が失敗。全部そろわないと困るとき
Promise.allSettled() 全部待って成否を返す。取れた分だけ使いたいとき
Promise.any() 1つ成功したらそれを返す
Promise.race() 成否を問わず最初に決着したものを返す

any() は失敗を飛ばして成功を待ちます。 race() は失敗でも最初なら返します。取り違えると、失敗が握りつぶされます。

よくある間違い

forEach の中で await する

空のままです。

forEach() はコールバック関数の戻り値を見ません。 async を付けても Promise が返るだけで、forEach() はそれを捨てて次へ進みます。 待っているつもりで、待っていません。

map() に変えて Promise.all() で待てば直ります。

forEach は非同期処理と組み合わせない、と覚えてしまうのが安全です。

全部いっぺんに投げる

要素が1000件あれば、Promise.all() は1000本を同時に走らせます。 相手のサーバーに断られたり、こちらの資源が尽きたりします。

数を区切ってください。

塊の中は並列、塊同士は直列になります。 size で同時に走る数を決められます。

最後の塊は端数(1件)になります。slice() は範囲を超えても落ちないので、割り切れなくても書き足す必要はありません。 → 文字列を切り出す・整える

まとめ

  • map() で先に始めて Promise.all() で待つ。結果は渡した順
  • 待ち時間のある処理にだけ効く。計算は速くならない
  • Promise.all() は1つ失敗すると全部失敗。成功分も受け取れない
  • 取れた分だけ使うなら Promise.allSettled()
  • forEach の中の await は待たない。 map() に変える
  • 件数が多いときは塊に区切って同時数を抑える

forEach() と map() の違いそのもの(戻り値を見るか捨てるか)は、用語のページで詳しく扱っています。 → Array.prototype.map()

この記事の根拠

  1. Promise.all — ECMAScript® 2026 Language Specificationtc39.es
  2. Promise.allSettled — ECMAScript® 2026 Language Specificationtc39.es
  3. Promise — MDNdeveloper.mozilla.org

掲載しているコード例は、公開前に読み手が押したときに動くのと同じもので実行して出力を突き合わせています。結果はリポジトリに残しています。

このページの原文(Markdown)