在 forEach 循环中使用 async/await

IT技术 javascript node.js promise async-await ecmascript-2017
2021-01-06 21:33:51

循环中使用async/有什么问题吗?我正在尝试遍历文件数组和每个文件的内容。awaitforEachawait

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

6个回答

当然代码确实有效,但我很确定它不会做你期望它做的事情。它只是触发多个异步调用,但该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确实无法使用每个async回调函数调用都会返回一个Promise,但您将它们扔掉而不是等待它们。只需使用map,您就可以等待您将获得的一系列PromisePromise.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)
  }));
}
@Taurus 如果您不打算等待它们,那么for…of同样适用于forEach. 不,我的意思是这一段强调.forEach在现代 JS 代码中没有位置
2021-02-13 21:33:51
好的,我知道为什么......使用 Babel 会将async/转换await为生成器函数,而 usingforEach意味着每次迭代都有一个单独的生成器函数,与其他的无关。所以他们将独立执行,没有next()与其他人的背景实际上,一个简单的for()循环也可以工作,因为迭代也是在一个单独的生成器函数中。
2021-02-21 21:33:51
你能解释一下为什么for ... of ...有效吗?
2021-02-22 21:33:51
@Demonbane:简而言之,因为它旨在工作:-)await暂停当前函数评估,包括所有控制结构。是的,在这方面它与生成器非常相似(这就是为什么它们被用来填充 async/await)。
2021-03-05 21:33:51
@arve0 不是真的,async函数与Promise执行器回调完全不同,但是map回调在两种情况下都返回一个Promise。
2021-03-06 21:33:51

使用 ES2018,您可以大大简化上述所有答案:

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

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

参见规范:proposal-async-iteration


2018-09-10:这个答案最近受到了很多关注,有关异步迭代的更多信息,请参阅Axel Rauschmayer 的博客文章

我们如何将files数组委托fs.readFile这里?它从可迭代?
2021-02-10 21:33:51
使用这个解决方案,每次迭代都会等待前一个,如果操作正在进行一些长计算或读取一个长文件,它会阻止下一个的执行,而不是将所有函数映射到 promises 并等待它们完成.
2021-02-11 21:33:51
我不认为这个答案解决了最初的问题。for-await-of使用同步迭代(在我们的例子中是一个数组)不包括在每次迭代中使用异步操作同时迭代一个数组的情况。如果我没记错的话,for-await-of在 non-promise 值上使用同步迭代与使用普通的for-of.
2021-02-16 21:33:51
为什么人们会赞成这个答案?仔细查看答案、问题和建议。之后of应该是将返回数组的异步函数。它不起作用,弗朗西斯科说;
2021-02-21 21:33:51
此答案与 OP 存在相同的问题:它并行访问所有文件。结果的序列化打印只是隐藏了它。
2021-03-05 21:33:51

而不是Promise.all结合 with Array.prototype.map(它不保证Promises 被解析的顺序),我使用Array.prototype.reduce,从一个已解决的开始Promise

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

  await files.reduce(async (promise, file) => {
    // This line will wait for the last async function to finish.
    // The first iteration uses an already resolved Promise
    // so, it will immediately continue.
    await promise;
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  }, Promise.resolve());
}
@Shay,您的意思是顺序,而不是同步。这仍然是异步的 - 如果安排了其他事情,它们将在此处的迭代之间运行。
2021-02-19 21:33:51
这工作完美,非常感谢。你能用Promise.resolve()and解释一下这里发生了什么await promise;吗?
2021-02-26 21:33:51
这很酷。我认为文件将按顺序读取而不是一次读取是否正确?
2021-02-26 21:33:51
@parrker9Promise.resolve()返回一个已经解析的Promise对象,所以它reduce有一个Promise开始。await promise;将等待Promise链中的最后一个解决。@GollyJer 文件将按顺序处理,一次一个。
2021-02-27 21:33:51
如果您需要异步进程尽快完成并且您不关心它们是否按顺序完成,请尝试使用提供的具有大量赞成票的解决方案之一Promise.all例子:Promise.all(files.map(async (file) => { /* code */ }));
2021-03-07 21:33:51

npm 上p-iterationmodule实现了 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);
  });
})();

这里有一些forEachAsync原型。请注意,您需要使用await它们:

Array.prototype.forEachAsync = async function (fn) {
    for (let t of this) { await fn(t) }
}

Array.prototype.forEachAsyncParallel = async function (fn) {
    await Promise.all(this.map(fn));
}

请注意,虽然您可以将其包含在您自己的代码中,但您不应将其包含在您分发给其他人的库中(以避免污染他们的全局变量)。

用法:等待 myArray。forEachAsyncParallel( async (item) => { await myAsyncFunction(item) })
2021-02-17 21:33:51