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


当前回答

从循环调用异步方法是不好的。这是因为每个循环迭代都将延迟到整个异步操作完成。这不是很有表演性。它还避免了异步/等待的并行化优势。

更好的解决方案是一次创建所有Promise,然后使用Promise.all()访问结果。否则,在前一个操作完成之前,每个后续操作都不会启动。

因此,代码可以按如下方式重构:;

const printFiles = async () => {
  const files = await getFilePaths();
  const results = [];
  files.forEach((file) => {
    results.push(fs.readFile(file, 'utf8'));
  });
  const contents = await Promise.all(results);
  console.log(contents);
}

其他回答

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

例如:

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

to

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

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

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);
  });
})();

下面是一些forEachAsync原型。注意,您需要等待他们:

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));
}

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

@贝吉已经给出了如何正确处理这一特殊案件的答案。我不会在这里重复。

我想解决在异步和等待时使用forEach和for循环之间的区别

forEach的工作原理

让我们看看forEach是如何工作的。根据ECMAScript规范,MDN提供了一种可以用作polyfill的实现。我将其复制并粘贴到此处,并删除注释。

Array.prototype.forEach = function (callback, thisArg) {
  if (this == null) { throw new TypeError('Array.prototype.forEach called on null or undefined'); }
  var T, k;
  var O = Object(this);
  var len = O.length >>> 0;
  if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function'); }
  if (arguments.length > 1) { T = thisArg; }
  k = 0;
  while (k < len) {
    var kValue;
    if (k in O) {
      kValue = O[k];
      callback.call(T, kValue, k, O); // pay attention to this line
    }
    k++;
  }
};

让我们回到代码,将回调作为函数提取。

async function callback(file){
  const contents = await fs.readFile(file, 'utf8')
  console.log(contents)
}

所以,回调基本上返回一个promise,因为它是用异步声明的。在forEach内部,回调只是以正常方式调用,如果回调本身返回一个promise,javascript引擎不会等待它被解析或拒绝。相反,它将承诺放入作业队列中,并继续执行循环。

如何在回调中等待fs.readFile(文件,'utf8')?

基本上,当异步回调有机会被执行时,js引擎将暂停,直到fs.readFile(文件,'utf8')被解析或拒绝,并在完成后继续执行异步函数。因此contents变量存储fs.readFile的实际结果,而不是promise。因此,console.log(contents)注销文件内容,而不是Promise

为什么。。。作品?

当我们编写循环的泛型for时,我们获得了比forEach更多的控制权。让我们重构printFiles。

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

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

当为循环求值时,我们在异步函数中有await promise,执行将暂停,直到await promice得到解决。因此,您可以认为文件是按确定的顺序逐个读取的。

按顺序执行

有时,我们确实需要以顺序执行异步函数。例如,我有几个新记录存储在一个数组中,要保存到数据库中,我希望它们按顺序保存,这意味着数组中的第一个记录应该先保存,然后再保存,直到保存最后一个记录。

下面是一个示例:

常量记录=[1,2,3,4];异步函数saveRecord(record){return new Promise((已解决,已拒绝)=>{setTimeout(()=>{已解析(`record${record}已保存`)},数学随机(*500)});}EachSaveRecords(记录)的异步函数{records.forEach(异步(记录)=>{const res=等待saveRecord(记录);console.log(res);})}SaveRecords(记录)的异步函数{for(记录的常量记录){const res=等待saveRecord(记录);console.log(res);}}(异步()=>{console.log(“===保存记录的===”)等待保存记录(记录)console.log(“==对于每个保存记录==”)等待EachSaveRecords(记录)})()

我使用setTimeout来模拟将记录保存到数据库的过程——这是异步的,花费了随机时间。使用forEach,记录将按未确定的顺序保存,但使用for。。的,它们按顺序保存。

我会使用经过良好测试(每周下载数百万次)的pify和异步模块。如果您不熟悉异步模块,我强烈建议您查看它的文档。我见过多个开发人员浪费时间重新创建其方法,或者更糟的是,当高阶异步方法会简化代码时,很难维护异步代码。

const async=要求('async')const fs=要求('s-fromise')const pify=要求('pify')异步函数getFilePaths(){return Promise.resolve(['./“package.json”,'./package-lock.json',]);}异步函数printFiles(){const files=等待getFilePaths()await pify(async.eachSeries)(files,async(file)=>{//<--串联运行//await pify(async.each)(files,async(file)=>{//<--并行运行const contents=await fs.readFile(文件,'utf8')console.log(内容)})console.log('HAMBONE')}printFiles().then(()=>{console.log('HAMBUNY')})//日志顺序://package.json内容//package-lock.json内容//汉堡//汉布尼```