在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,所以我只想问问这是否有问题。


当前回答

如果要同时迭代所有元素:

async function asyncForEach(arr, fn) {
  await Promise.all(arr.map(fn));
}

如果您希望非并发地遍历所有元素(例如,当映射函数具有副作用或同时在所有数组元素上运行mapper时,资源成本太高):

选项A:承诺

function asyncForEachStrict(arr, fn) {
  return new Promise((resolve) => {
    arr.reduce(
      (promise, cur, idx) => promise
        .then(() => fn(cur, idx, arr)),
      Promise.resolve(),
    ).then(() => resolve());
  });
}

选项B:异步/等待

async function asyncForEachStrict(arr, fn) {
  for (let idx = 0; idx < arr.length; idx += 1) {
    const cur = arr[idx];

    await fn(cur, idx, arr);
  }
}

其他回答

然而,上述两种解决方案都有效,Antonio的代码更少,这是它如何帮助我从数据库中解析数据,从几个不同的子引用中,然后将它们全部推到一个数组中,并在完成所有任务后以承诺的方式进行解析:

Promise.all(PacksList.map((pack)=>{
    return fireBaseRef.child(pack.folderPath).once('value',(snap)=>{
        snap.forEach( childSnap => {
            const file = childSnap.val()
            file.id = childSnap.key;
            allItems.push( file )
        })
    })
})).then(()=>store.dispatch( actions.allMockupItems(allItems)))

如果要同时迭代所有元素:

async function asyncForEach(arr, fn) {
  await Promise.all(arr.map(fn));
}

如果您希望非并发地遍历所有元素(例如,当映射函数具有副作用或同时在所有数组元素上运行mapper时,资源成本太高):

选项A:承诺

function asyncForEachStrict(arr, fn) {
  return new Promise((resolve) => {
    arr.reduce(
      (promise, cur, idx) => promise
        .then(() => fn(cur, idx, arr)),
      Promise.resolve(),
    ).then(() => resolve());
  });
}

选项B:异步/等待

async function asyncForEachStrict(arr, fn) {
  for (let idx = 0; idx < arr.length; idx += 1) {
    const cur = arr[idx];

    await fn(cur, idx, arr);
  }
}

npm上的p-iteration模块实现了Array迭代方法,因此它们可以以非常简单的方式与async/await一起使用。

您的案例示例:

const { forEach } = require('p-iteration');
const fs = require('fs-promise');

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

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

您可以使用Array.prototype.forEach,但async/await不太兼容。这是因为从异步回调返回的promise需要解析,但Array.prototype.forEach不会解析其回调执行中的任何promise。因此,您可以使用forEach,但您必须自己处理承诺决议。

以下是使用Array.prototype.forEach读取和打印每个文件的方法

async function printFilesInSeries () {
  const files = await getFilePaths()

  let promiseChain = Promise.resolve()
  files.forEach((file) => {
    promiseChain = promiseChain.then(() => {
      fs.readFile(file, 'utf8').then((contents) => {
        console.log(contents)
      })
    })
  })
  await promiseChain
}

这里有一种并行打印文件内容的方法(仍然使用Array.protocol.forEach)

async function printFilesInParallel () {
  const files = await getFilePaths()

  const promises = []
  files.forEach((file) => {
    promises.push(
      fs.readFile(file, 'utf8').then((contents) => {
        console.log(contents)
      })
    )
  })
  await Promise.all(promises)
}

替换forEach()等待循环的一个简单的解决方案是用map替换forEach,并在开头添加Promise.all()。

例如:

await y.forEach(异步(x)=>{

to

await Promise.all(y.map(异步(x)=>{

结尾处需要一个额外的)。