Promise.all で同時に待つ
このレッスンで分かること
Promise.allは Promise の配列を受け取り、結果の配列を返します- 結果の並び順は、渡した配列の並び順のままになります
5 件取るのに、5 回ぶん待っている
前の結果を次に使うなら await を縦に並べるのが正解でした。ところが、互いに関係の無いものまで縦に並べると、待ち時間が丸ごと足し算になります。
JavaScript
// 1 件 80 ミリ秒として、5 件で 400 ミリ秒かかる
async function collectIcons(skills) {
const icons = [];
for (const skill of skills) {
icons.push(await fetchIcon(skill));
}
return icons;
}for-of の中の await は、1 件が終わってから次を始めます。5 件目に取りかかるのは 320 ミリ秒後です。しかし 5 件は互いに無関係なので、本当は全部いっぺんに始めてよいはずです。
| 書き方 | 80 ミリ秒かかるものが 5 件のとき |
|---|---|
for-of の中で await する | 400 ミリ秒 |
Promise.all でまとめて待つ | 80 ミリ秒 |
配列を渡して、配列を受け取る
Promise.all に Promise の配列を渡すと、全部そろってから結果の配列を返す 1 つの Promise が返ります。
JavaScript
const [photo, bio, skills] = await Promise.all([
fetchPhoto(),
fetchBio(),
fetchSkills()
]);配列を作った時点で、3 つの処理はもう動き始めています。Promise.all は始める道具ではなく、始まっているものを待ち合わせる道具です。
結果の並びは、渡した配列の並びと同じです。早く終わったものが前に来ることはありません。3 つ目が最初に終わっても、結果の 3 番目に入ります。だから上のように分割代入で受け取れますし、元の配列と添字でつき合わせることもできます。
件数が決まっていない配列を相手にするときは map が使えます。
JavaScript
const icons = await Promise.all(
skills.map(function (skill) {
return fetchIcon(skill);
})
);skills.map(...) が返すのは Promise の配列で、値の配列ではありません。ここに await を書かずそのまま使うと、Promise 同士を足したり比べたりすることになり、[object Promise] のような文字列ができあがります。なお map に渡す関数は Promise をそのまま返せばよく、async と await を付ける必要はありません。待つのは Promise.all 側の仕事です。
空の配列を渡しても動きます。Promise.all([]) はすぐに空の配列で確定するので、0 件のときの分岐を別に書かなくて済みます。
1 つでも失敗すると、全部失敗する
渡した中の 1 つが失敗した瞬間、Promise.all はその理由で失敗します。残りの結果は捨てられ、待ってもくれません。全部そろわないと画面を作れない場面ではこの性格が正しく働きますが、取れたぶんだけ出したい場面には向きません。
もう 1 つ知っておきたいのが、失敗しても残りの処理は止まらないという点です。取り消す仕組みは入っていません。
JavaScript
const results = await Promise.all([
fetchPhoto(), // 80 ミリ秒後に成功する
fetchMissing() // 10 ミリ秒後に失敗する
]);10 ミリ秒後に Promise.all は失敗しますが、1 つ目は 80 ミリ秒後にきちんと成功します。その結果を受け取る人がいないだけです。Promise.all を使った時点で、全か無かを選んでいる、と覚えてください。
よくある間違い
- 配列に入れる前に
awaitしてしまう — 下の書き方だと、配列に入るのは Promise ではなく値です。Promise.allに渡すころにはすべて終わっていて、同時に動かした意味がありません
JavaScript
const promises = [];
for (const skill of skills) {
promises.push(await fetchIcon(skill)); // ここで待ってしまう
}
const icons = await Promise.all(promises);- 配列にせず、引数を並べて渡す — 引数は 1 つだけで、そこに配列を渡します。
Promise.all(a, b)と書くと 2 つ目は無視され、結果が 1 件ぶんしか返りません。かっこの位置を見直してください
要件
- fetchViews(work) を作る。80 ミリ秒後に work.views を resolve する Promise を返す
- loadTotalViews(works) を async function として作り、map で作った Promise の配列を Promise.all に渡す
- await した結果から、count に件数、total に表示回数の合計を入れたオブジェクトを返す
- 空の配列を渡されたときは count も total も 0 にする
入出力例
loadTotalViews([{"id":1,"published":true,"title":"プロフィールサイト","views":120,"year":2026},{"id":2,"published":true,"title":"自己紹介カード","views":48,"year":2026},{"id":3,"published":true,"title":"スキル一覧の表","views":32,"year":2026},{"id":4,"published":true,"title":"作品ギャラリー","views":76,"year":2026},{"id":5,"published":false,"title":"学習ログ","views":0,"year":2025}]) → {"count":5,"total":276}
loadTotalViews([]) → {"count":0,"total":0}
loadTotalViews([{"id":5,"published":false,"title":"学習ログ","views":0,"year":2025}]) → {"count":1,"total":0}
loadTotalViews([{"id":1,"published":true,"title":"プロフィールサイト","views":120,"year":2026},{"id":2,"published":true,"title":"自己紹介カード","views":48,"year":2026}]) → {"count":2,"total":168}