Promise.all を使ってループ内 await を回避する

こんにちは、 Gaji-Labo フロントエンドエンジニアの茶木です。

「このメソッドはどういうときに使うんだろう?」

ドキュメントを読んでもわからないけど、ちょうどいいユースケースを通すと理解できることってありますね。ちょうど最近その体験をしたのが Promise.all です。

まず、良くないコードをご覧ください。

// 商品のidから重さの合計を求める
async function getWeight(itemIds: string[]): number {
  const results = [];
  for (const itemId of itemIds) {
    results.push(await getItemApi(itemId));
  }
  return results.reduce((sum, item) => sum + item.weight, 0);
}

配列の非同期処理を行いたいケースです。

ループの中で await を呼び出しています。
一見、問題なさそうですが、この書き方ではESLint が次の警告を出します。

https://eslint.org/docs/rules/no-await-in-loop

async にある並列処理の機能が生かされません、という旨の警告です。

たとえば、itemIds の配列の長さが 100、各非同期処理が 100ms かかるとします。この場合、並列処理が行われないと処理が10秒止まってしまうことになります。これは由々しき事態ですね。

なぜ、こうなってしまうかというとループの中で await を記述しているために、各非同期処理が完了するまで次のループの処理が開始されないからです。

次に正しいコードです。

// 商品のidから重さの合計を求める
async function getWeight(itemIds: string[]): number {
  const results = [];
  for (const itemId of itemIds) {
    results.push(getItemApi(itemId));
  }
  const items = (await Promise.all(results))
  return items.reduce((sum, item) => sum + item.weight, 0);
}

まず、for ループの中から await を外します。これにより for ループ内の処理は開始されたら、即座に次のループに移るようになります。await を外したので results は処理の結果の配列ではなく、プロミスの配列になります。

ここで Promise.all の出番です。

Promise.allfor ループの外に出します。Promise.all は渡したプロミスの配列のすべての終了を待ちます。await と組み合わせることで、非同期処理の結果を配列にまとめることができます。この結果の配列は、処理の終了順に関わらず元のプロミスの配列の順番と対応しています。

Promise.all まとめ

非同期処理のループの定型文のように覚えても良さそうです。
複数のプロミスをひとつのプロミスのように扱えるので、読みやすくて良いですね。

Gaji-Laboでは、React経験が豊富なフロントエンドエンジニアを募集しています

弊社ではReactの知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違うGaji-Laboを味わいに来ませんか?

もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!

求人応募してみる!

投稿者 Chaki Hironori

webライターもやってるフロントエンドエンジニアです。Reactは自信があります。またデザイン畑の出身で、気持ちのいいアニメーションやインタラクティブな表現は丁寧に手掛けます。好きなものは中南米の遺跡で、スペイン語が少しできます。