async/awaitとforEachループを併用してPromiseの結果を取り出す


スポンサーリンク

forEachループでasync/awaitを使用することに問題はありますか?ファイルの配列をループして、各ファイルの内容を待ち受けようとしています。

import fs from 'fs-promise'

async function printFiles () {
  const files = await getFilePaths() // Assume this works fine

  files.forEach(async (file) => {
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
  })
}

printFiles()

このコードは動作しますが、何か問題があるのでしょうか?このような高次関数ではasync/awaitを使ってはいけないと誰かに言われたので、何か問題があるのかどうかを聞きたかったのです。

回答

確かにこのコードは動作しますが、あなたが期待していることをしていないのは確かです。複数の非同期呼び出しを行っていますが、printFiles関数はその後すぐに戻ります。

ファイルを順番に読みたい場合は、forEachを使ってはいけません。代わりに現代的な for ... of ループを使えば、 await は期待通りに動作します。

async function printFiles () {
  const files = await getFilePaths();

  for (const file of files) {
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  }
}

並行してファイルを読みたい場合は、forEachを確かに使うことはできません。非同期コールバック関数の呼び出しはそれぞれプロミスを返しますが、それを待つのではなく捨てています。代わりにmapを使えば、Promise.allで得られるプロミスの配列を待ち受けることができます。

async function printFiles () {
  const files = await getFilePaths();

  await Promise.all(files.map(async (file) => {
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
  }));
}

https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop