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


当前回答

下面是一些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));
}

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

其他回答

我使用Array.prototype.reduce代替Promise.all和Array.prototy.map(这不能保证Promise的解析顺序),从解析的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());
}

与Antonio Val的p迭代类似,另一种npm模块是异步af:

const AsyncAF = require('async-af');
const fs = require('fs-promise');

function printFiles() {
  // since AsyncAF accepts promises or non-promises, there's no need to await here
  const files = getFilePaths();

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

printFiles();

或者,async-af有一个静态方法(log/logAF)来记录promise的结果:

const AsyncAF = require('async-af');
const fs = require('fs-promise');

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

  AsyncAF(files).forEach(file => {
    AsyncAF.log(fs.readFile(file, 'utf8'));
  });
}

printFiles();

然而,该库的主要优点是您可以链接异步方法来执行以下操作:

const aaf = require('async-af');
const fs = require('fs-promise');

const printFiles = () => aaf(getFilePaths())
  .map(file => fs.readFile(file, 'utf8'))
  .forEach(file => aaf.log(file));

printFiles();

异步af

这里是一个在forEach循环中使用异步的好例子。

编写自己的asyncForEach

async function asyncForEach(array, callback) {  
    for (let index = 0; index < array.length; index++) {
        await callback(array[index], index, array)
    }
}

你可以这样用

await asyncForEach(array, async function(item,index,array){
     //await here
   }
)

此解决方案还对内存进行了优化,因此您可以在10000个数据项和请求上运行它。这里的一些其他解决方案将使大型数据集上的服务器崩溃。

在TypeScript中:

export async function asyncForEach<T>(array: Array<T>, callback: (item: T, index: number) => Promise<void>) {
        for (let index = 0; index < array.length; index++) {
            await callback(array[index], index);
        }
    }

如何使用?

await asyncForEach(receipts, async (eachItem) => {
    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。每一个异步回调函数调用都会返回一个promise,但您要丢弃它们而不是等待它们。只需使用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)
  }));
}