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.all
は for
ループの外に出します。Promise.all
は渡したプロミスの配列のすべての終了を待ちます。await と組み合わせることで、非同期処理の結果を配列にまとめることができます。この結果の配列は、処理の終了順に関わらず元のプロミスの配列の順番と対応しています。
Promise.all まとめ
非同期処理のループの定型文のように覚えても良さそうです。
複数のプロミスをひとつのプロミスのように扱えるので、読みやすくて良いですね。
Gaji-Laboでは、React経験が豊富なフロントエンドエンジニアを募集しています
弊社ではReactの知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違うGaji-Laboを味わいに来ませんか?
もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!
求人応募してみる!