将 async/await 与 forEach 循环一起使用

Using async/await with a forEach loop

提问人:Saad 提问时间:6/2/2016 最后编辑:Nikita FedyashevSaad 更新时间:9/4/2023 访问量:2066335

问:

循环使用 / 有任何问题吗?我正在尝试遍历文件数组和每个文件的内容。asyncawaitforEachawait

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

这段代码确实有效,但这会出问题吗?有人告诉我,你不应该在这样的高阶函数中使用 /,所以我只是想问一下这是否有任何问题。asyncawait

JavaScript 节点.js 承诺 步-await ecmascript-2017

评论


答:

5468赞 Bergi 6/2/2016 #1

当然,代码确实有效,但我很确定它不会按照您的期望执行操作。它只是触发多个异步调用,但该函数在那之后会立即返回。printFiles

按顺序读取

如果要按顺序读取文件,则不能使用 forEach。只需改用新式循环,即可按预期工作:for … ofawait

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

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

并行读取

如果要并行读取文件,则不能使用 forEach。每个回调函数调用都会返回一个 promise,但您正在丢弃它们而不是等待它们。只需改用,您就可以等待您将获得的一系列承诺:asyncmapPromise.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)
  }));
}

评论

99赞 Demonbane 8/16/2016
你能解释一下为什么有效吗?for ... of ...
242赞 Demonbane 8/16/2016
好吧,我知道为什么......使用 Babel 将 transform / to generator 函数和 using 意味着每次迭代都有一个单独的生成器函数,与其他函数无关。因此,它们将独立执行,并且没有与他人相关的上下文。实际上,一个简单的循环也有效,因为迭代也位于一个生成器函数中。asyncawaitforEachnext()for()
50赞 Bergi 8/16/2016
@Demonbane:简而言之,因为它被设计为工作:-) 暂停当前功能评估,包括所有控制结构。是的,在这方面它与生成器非常相似(这就是为什么它们用于 polyfill async/await 的原因)。await
8赞 Bergi 3/30/2017
@arve0 并非如此,函数与执行器回调完全不同,但是的,在这两种情况下,回调都会返回一个 promise。asyncPromisemap
6赞 Bergi 3/20/2018
@Taurus 如果您不打算等待它们,那么同样可以工作.不,我真的是想强调这一段在现代 JS 代码中没有位置。for…offorEach.forEach
61赞 Antonio Val 7/10/2017 #2

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);
  });
})();
7赞 Hooman Askari 8/26/2017 #3

上述两种解决方案都有效,但是,Antonio 用更少的代码完成了这项工作,以下是它如何帮助我从数据库中解析数据,来自几个不同的子引用,然后将它们全部推送到一个数组中,并在完成所有操作后以 promise 的形式解析它:

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)))
12赞 Jay Edwards 9/23/2017 #4

在文件中弹出几个方法非常轻松,这些方法将按序列化顺序处理异步数据,并为您的代码提供更传统的味道。例如:

module.exports = function () {
  var self = this;

  this.each = async (items, fn) => {
    if (items && items.length) {
      await Promise.all(
        items.map(async (item) => {
          await fn(item);
        }));
    }
  };

  this.reduce = async (items, fn, initialValue) => {
    await self.each(
      items, async (item) => {
        initialValue = await fn(initialValue, item);
      });
    return initialValue;
  };
};

现在,假设它保存在“./myAsync.js”中,您可以在相邻文件中执行类似于以下内容的操作:

...
/* your server setup here */
...
var MyAsync = require('./myAsync');
var Cat = require('./models/Cat');
var Doje = require('./models/Doje');
var example = async () => {
  var myAsync = new MyAsync();
  var doje = await Doje.findOne({ name: 'Doje', noises: [] }).save();
  var cleanParams = [];

  // FOR EACH EXAMPLE
  await myAsync.each(['bork', 'concern', 'heck'], 
    async (elem) => {
      if (elem !== 'heck') {
        await doje.update({ $push: { 'noises': elem }});
      }
    });

  var cat = await Cat.findOne({ name: 'Nyan' });

  // REDUCE EXAMPLE
  var friendsOfNyanCat = await myAsync.reduce(cat.friends,
    async (catArray, friendId) => {
      var friend = await Friend.findById(friendId);
      if (friend.name !== 'Long cat') {
        catArray.push(friend.name);
      }
    }, []);
  // Assuming Long Cat was a friend of Nyan Cat...
  assert(friendsOfNyanCat.length === (cat.friends.length - 1));
}

评论

3赞 Jay Edwards 9/26/2017
小附录,别忘了将你的 await/asyncs 包装在 try/catch 块中!!
9赞 LeOn - Han Li 9/25/2017 #5

一个重要的警告是:方法和方式实际上具有不同的效果。await + for .. offorEach + async

在真正的循环中,将确保所有异步调用都逐个执行。并且这种方式将同时触发所有承诺,这更快,但有时会不知所措(如果您进行一些数据库查询或访问一些有容量限制的 Web 服务并且不想一次触发 100,000 个调用)。awaitforforEach + async

如果您不使用并希望确保文件一个接一个地读取,您也可以使用(不太优雅)。reduce + promiseasync/await

files.reduce((lastPromise, file) => 
 lastPromise.then(() => 
   fs.readFile(file, 'utf8')
 ), Promise.resolve()
)

或者,您可以创建一个 forEachAsync 来提供帮助,但基本上使用相同的 for 循环底层。

Array.prototype.forEachAsync = async function(cb){
    for(let x of this){
        await cb(x);
    }
}

评论

0赞 Bergi 11/16/2017
看看如何在 Array.prototype 和 Object.prototype 上的 javascript 中定义方法,以便它不会出现在 for in 循环中。此外,您可能应该使用与本机相同的迭代 - 访问索引而不是依赖可迭代性 - 并将索引传递给回调。forEach
0赞 Timothy Zorn 3/27/2018
可以使用异步函数的方式使用。我在回答中展示了一个示例:stackoverflow.com/a/49499491/2537258Array.prototype.reduce
-2赞 Zachary Ryan Smith 2/5/2018 #6

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

const async = require('async')
const fs = require('fs-promise')
const pify = require('pify')

async function getFilePaths() {
    return Promise.resolve([
        './package.json',
        './package-lock.json',
    ]);
}

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

  await pify(async.eachSeries)(files, async (file) => {  // <-- run in series
  // await pify(async.each)(files, async (file) => {  // <-- run in parallel
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
  })
  console.log('HAMBONE')
}

printFiles().then(() => {
    console.log('HAMBUNNY')
})
// ORDER OF LOGS:
// package.json contents
// package-lock.json contents
// HAMBONE
// HAMBUNNY
```

评论

1赞 jbustamovej 2/20/2018
这是朝着错误方向迈出的一步。这是我创建的映射指南,旨在帮助人们陷入回调地狱,进入现代 JS 时代:github.com/jmjpro/async-package-to-async-await/blob/master/...
0赞 Zachary Ryan Smith 2/21/2018
正如你在这里看到的,我对使用 async/await 而不是 async lib 感兴趣并愿意。现在,我认为每个人都有时间和地点。我不相信 async lib == “callback hell” 和 async/await == “现代 JS 时代”。imo,当 async lib > async/await 时: 1. 复杂的流程(例如,队列、货物,甚至在事情变得复杂时自动) 2.并发 3.支持数组/对象/可迭代对象 4.错误处理
18赞 chharvey 2/23/2018 #7

除了@Bergi的答案之外,我还想提供第三种选择。这与@Bergi的第二个示例非常相似,但不是单独等待每个示例,而是创建一系列承诺,每个承诺在最后等待。readFile

import fs from 'fs-promise';
async function printFiles () {
  const files = await getFilePaths();

  const promises = files.map((file) => fs.readFile(file, 'utf8'))

  const contents = await Promise.all(promises)

  contents.forEach(console.log);
}

请注意,传递给的函数不需要是 ,因为无论如何都会返回一个 Promise 对象。因此是一个 Promise 对象数组,可以将其发送到 。.map()asyncfs.readFilepromisesPromise.all()

在 @Bergi 的回答中,控制台可能会按照读取顺序记录文件内容。例如,如果一个非常小的文件在一个非常大的文件之前完成读取,那么即使数组中的小文件在大文件之后,它也会首先被记录下来。但是,在我上面的方法中,您可以保证控制台将按照与提供的数组相同的顺序记录文件。files

4赞 Babakness 2/28/2018 #8

使用 Task、futurize 和可遍历列表,您可以简单地执行以下操作

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

  List(files).traverse( Task.of, f => readFile( f, 'utf-8'))
    .fork( console.error, console.log)
}

以下是您的设置方式

import fs from 'fs';
import { futurize } from 'futurize';
import Task from 'data.task';
import { List } from 'immutable-ext';

const future = futurizeP(Task)
const readFile = future(fs.readFile)

构建所需代码的另一种方法是

const printFiles = files => 
  List(files).traverse( Task.of, fn => readFile( fn, 'utf-8'))
    .fork( console.error, console.log)

或者甚至更注重功能

// 90% of encodings are utf-8, making that use case super easy is prudent

// handy-library.js
export const readFile = f =>
  future(fs.readFile)( f, 'utf-8' )

export const arrayToTaskList = list => taskFn => 
  List(files).traverse( Task.of, taskFn ) 

export const readFiles = files =>
  arrayToTaskList( files, readFile )

export const printFiles = files => 
  readFiles(files).fork( console.error, console.log)

然后从父函数

async function main() {
  /* awesome code with side-effects before */
  printFiles( await getFiles() );
  /* awesome code with side-effects after */
}

如果你真的想要在编码方面有更大的灵活性,你可以这样做(为了好玩,我使用的是建议的 Pipe Forward 运算符 )

import { curry, flip } from 'ramda'

export const readFile = fs.readFile 
  |> future,
  |> curry,
  |> flip

export const readFileUtf8 = readFile('utf-8')

PS - 我没有在控制台上尝试此代码,可能有一些错别字......“直线自由泳,从穹顶顶上下来!”正如90年代的孩子们所说的那样。:-p

52赞 Matt 3/22/2018 #9

以下是一些原型。请注意,您需要:forEachAsyncawait

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

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

评论

1赞 Damien Romito 6/2/2021
用法:await myArray。forEachAsyncParallel( async (item) => { await myAsyncFunction(item) })
0赞 Normal 6/22/2022
@Matt,如果它不是异步的,等待不是问题吗?如果给定的输入是同步函数呢?stackoverflow.com/a/53113299/18387350fn
200赞 Timothy Zorn 3/27/2018 #10

我没有与 (这不能保证 s 的解析顺序)结合使用,而是使用 ,从 resolved 开始:Promise.allArray.prototype.mapPromiseArray.prototype.reducePromise

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

评论

4赞 parrker9 3/29/2018
这效果很好,非常感谢。你能解释一下这里发生了什么吗?Promise.resolve()await promise;
2赞 GollyJer 6/9/2018
这很酷。我认为文件将按顺序读取而不是一次全部读取是正确的吗?
7赞 Timothy Zorn 6/17/2018
@parrker9返回一个已解析的对象,因此该对象有一个开始。 将等待链中的最后一个解决。@GollyJer 文件将按顺序处理,一次处理一个。Promise.resolve()PromisereducePromiseawait promise;Promise
3赞 Timothy Zorn 5/31/2019
@Shay,你的意思是顺序的,而不是同步的。这仍然是异步的 - 如果安排了其他事情,它们将在这里的迭代之间运行。
6赞 Timothy Zorn 2/1/2020
如果您需要异步进程尽快完成,并且您不关心它们是否按顺序完成,请尝试提供的解决方案之一,该解决方案具有大量赞成票,该解决方案使用 .例:Promise.allPromise.all(files.map(async (file) => { /* code */ }));
636赞 Cisco 6/15/2018 #11

使用 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

简化:

  for await (const results of array) {
    await longRunningTask()
  }
  console.log('I will wait')

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

评论

16赞 Antonio Val 1/9/2019
我不认为这个答案解决了最初的问题。 使用同步可迭代对象(在我们的例子中为数组)不包括在每次迭代中使用异步操作并发迭代数组的情况。如果我没记错的话,在非承诺值上使用同步可迭代物与使用普通 .for-await-offor-await-offor-of
3赞 Vadim Shvetsov 1/17/2019
我们如何将数组委托给这里?它从可迭代中获取?filesfs.readFile
1赞 Rafi Henig 9/11/2019
使用此解决方案,每次迭代都将等待上一次迭代,如果操作进行一些长时间的计算或读取长时间的文件,它将阻止下一次的执行,而不是将所有函数映射到 promise 并等待它们完成。
4赞 jib 2/18/2021
这个答案与 OP 有相同的问题:它并行访问所有文件。结果的序列化打印只是隐藏了它。
12赞 Bergi 12/13/2021
这个答案是错误的。 返回一个 promise 数组,而不是一个异步迭代器,这是为此而创建的!这将导致未处理的拒绝崩溃files.map()for await
1赞 Scott Rudiger 6/22/2018 #12

与 Antonio Val 的 p-iteration 类似,另一个 npm 模块是 async-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();

async-af

6赞 Beau 3/13/2019 #13

目前 Array.forEach 原型属性不支持异步操作,但我们可以创建自己的 poly-fill 来满足我们的需求。

// Example of asyncForEach Array poly-fill for NodeJs
// file: asyncForEach.js
// Define asynForEach function 
async function asyncForEach(iteratorFunction){
  let indexer = 0
  for(let data of this){
    await iteratorFunction(data, indexer)
    indexer++
  }
}
// Append it as an Array prototype property
Array.prototype.asyncForEach = asyncForEach
module.exports = {Array}

就是这样!现在,在操作之后定义的任何数组上都有一个异步 forEach 方法。

让我们来测试一下......

// Nodejs style
// file: someOtherFile.js

const readline = require('readline')
Array = require('./asyncForEach').Array
const log = console.log

// Create a stream interface
function createReader(options={prompt: '>'}){
  return readline.createInterface({
    input: process.stdin
    ,output: process.stdout
    ,prompt: options.prompt !== undefined ? options.prompt : '>'
  })
}
// Create a cli stream reader
async function getUserIn(question, options={prompt:'>'}){
  log(question)
  let reader = createReader(options)
  return new Promise((res)=>{
    reader.on('line', (answer)=>{
      process.stdout.cursorTo(0, 0)
      process.stdout.clearScreenDown()
      reader.close()
      res(answer)
    })
  })
}

let questions = [
  `What's your name`
  ,`What's your favorite programming language`
  ,`What's your favorite async function`
]
let responses = {}

async function getResponses(){
// Notice we have to prepend await before calling the async Array function
// in order for it to function as expected
  await questions.asyncForEach(async function(question, index){
    let answer = await getUserIn(question)
    responses[question] = answer
  })
}

async function main(){
  await getResponses()
  log(responses)
}
main()
// Should prompt user for an answer to each question and then 
// log each question and answer as an object to the terminal

我们可以对其他一些数组函数(如 map...

async function asyncMap(iteratorFunction){
  let newMap = []
  let indexer = 0
  for(let data of this){
    newMap[indexer] = await iteratorFunction(data, indexer, this)
    indexer++
  }
  return newMap
}

Array.prototype.asyncMap = asyncMap

...等等:)

需要注意的一些事项:

  • iteratorFunction 必须是异步函数或 promise
  • 之前创建的任何阵列都不具有此功能Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>
10赞 master_dodo 5/27/2019 #14

Bergi 的解决方案在基于承诺时效果很好。 您可以使用 ,或为此。fsbluebirdfs-extrafs-promise

但是,node 原生库的解决方案如下:fs

const result = await Promise.all(filePaths
    .map( async filePath => {
      const fileContents = await getAssetFromCache(filePath, async function() {

        // 1. Wrap with Promise    
        // 2. Return the result of the Promise
        return await new Promise((res, rej) => {
          fs.readFile(filePath, 'utf8', function(err, data) {
            if (data) {
              res(data);
            }
          });
        });
      });

      return fileContents;
    }));

注意:强制将函数作为第三个参数,否则抛出错误:require('fs')

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function
5赞 jgmjgm 10/15/2019 #15

要查看这是如何出错的,请在方法末尾打印 console.log。

一般情况下可能会出错的事情:

  • 任意顺序。
  • printFiles 可以在打印文件之前完成运行。
  • 性能不佳。

这些并不总是错误的,但在标准用例中经常是错误的。

通常,使用 forEach 将导致除最后一个之外的所有结果。它将在不等待函数的情况下调用每个函数,这意味着它告诉所有函数启动,然后在不等待函数完成的情况下完成。

import fs from 'fs-promise'

async function printFiles () {
  const files = (await getFilePaths()).map(file => fs.readFile(file, 'utf8'))

  for(const file of files)
    console.log(await file)
}

printFiles()

这是原生 JS 中的一个示例,它将保持秩序,防止函数过早返回,并在理论上保持最佳性能。

这将:

  • 启动所有文件读取以并行进行。
  • 通过使用 map 将文件名映射到 promise to wait for 来保留顺序。
  • 按照数组定义的顺序等待每个 promise。

使用此解决方案,第一个文件将在可用时立即显示,而无需等待其他文件首先可用。

它还将同时加载所有文件,而不必等待第一个文件完成,然后才能开始第二个文件读取。

这个版本和原始版本的唯一缺点是,如果同时启动多个读取,那么由于一次可能发生更多错误,因此处理错误会更加困难。

对于一次读取一个文件的版本,然后在失败时停止,而不会浪费时间尝试读取更多文件。即使使用精心设计的取消系统,也很难避免它在第一个文件上失败,但已经读取了大多数其他文件。

性能并不总是可预测的。虽然许多系统在并行文件读取方面会更快,但有些系统更喜欢顺序读取。有些是动态的,可能会在负载下移动,提供延迟的优化在大量争用下并不总是产生良好的吞吐量。

该示例中也没有错误处理。如果某些东西要求它们要么全部成功显示,要么根本不显示,它就不会这样做。

建议在每个阶段使用控制台 .log 进行深入实验,并使用假文件读取解决方案(而不是随机延迟)。尽管许多解决方案在简单情况下似乎都做同样的事情,但所有解决方案都有细微的差异,需要一些额外的审查才能挤出来。

使用此模拟来帮助区分解决方案:

(async () => {
  const start = +new Date();
  const mock = () => {
    return {
      fs: {readFile: file => new Promise((resolve, reject) => {
        // Instead of this just make three files and try each timing arrangement.
        // IE, all same, [100, 200, 300], [300, 200, 100], [100, 300, 200], etc.
        const time = Math.round(100 + Math.random() * 4900);
        console.log(`Read of ${file} started at ${new Date() - start} and will take ${time}ms.`)
        setTimeout(() => {
          // Bonus material here if random reject instead.
          console.log(`Read of ${file} finished, resolving promise at ${new Date() - start}.`);
          resolve(file);
        }, time);
      })},
      console: {log: file => console.log(`Console Log of ${file} finished at ${new Date() - start}.`)},
      getFilePaths: () => ['A', 'B', 'C', 'D', 'E']
    };
  };

  const printFiles = (({fs, console, getFilePaths}) => {
    return async function() {
      const files = (await getFilePaths()).map(file => fs.readFile(file, 'utf8'));

      for(const file of files)
        console.log(await file);
    };
  })(mock());

  console.log(`Running at ${new Date() - start}`);
  await printFiles();
  console.log(`Finished running at ${new Date() - start}`);
})();

6赞 PranavKAndro 11/25/2019 #16

今天我遇到了多种解决方案。在 forEach 循环中运行 async await 函数。通过构建包装器,我们可以实现这一目标。

此处的链接中提供了有关它在内部如何工作的更详细的说明,对于本机 forEach 以及为什么它无法进行异步函数调用以及有关各种方法的其他详细信息

可以通过多种方式完成它,它们如下,

方法1:使用包装器。

await (()=>{
     return new Promise((resolve,reject)=>{
       items.forEach(async (item,index)=>{
           try{
               await someAPICall();
           } catch(e) {
              console.log(e)
           }
           count++;
           if(index === items.length-1){
             resolve('Done')
           }
         });
     });
    })();

方法 2:使用与 Array.prototype 的泛型函数相同的函数

Array.prototype.forEachAsync.js

if(!Array.prototype.forEachAsync) {
    Array.prototype.forEachAsync = function (fn){
      return new Promise((resolve,reject)=>{
        this.forEach(async(item,index,array)=>{
            await fn(item,index,array);
            if(index === array.length-1){
                resolve('done');
            }
        })
      });
    };
  }

用法:

require('./Array.prototype.forEachAsync');

let count = 0;

let hello = async (items) => {

// Method 1 - Using the Array.prototype.forEach 

    await items.forEachAsync(async () => {
         try{
               await someAPICall();
           } catch(e) {
              console.log(e)
           }
        count++;
    });

    console.log("count = " + count);
}

someAPICall = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("done") // or reject('error')
        }, 100);
    })
}

hello(['', '', '', '']); // hello([]) empty array is also be handled by default

方法3:

使用 Promise.all

  await Promise.all(items.map(async (item) => {
        await someAPICall();
        count++;
    }));

    console.log("count = " + count);

方法4:传统的for循环或现代for循环

// Method 4 - using for loop directly

// 1. Using the modern for(.. in..) loop
   for(item in items){

        await someAPICall();
        count++;
    }

//2. Using the traditional for loop 

    for(let i=0;i<items.length;i++){

        await someAPICall();
        count++;
    }


    console.log("count = " + count);

评论

0赞 Bergi 11/25/2019
您的方法 1 和 2 只是不正确的实现,应该使用它们 - 它们没有考虑许多边缘情况中的任何一种。Promise.all
0赞 PranavKAndro 11/25/2019
@Bergi:感谢您的有效评论,请您解释一下为什么方法 1 和 2 不正确。它也达到了目的。这效果很好。也就是说,所有这些方法都是可能的,根据情况,人们可以决定选择一种方法。我有相同的运行示例。
0赞 Bergi 11/25/2019
它在空数组上失败,没有任何错误处理,可能还有更多问题。不要重新发明轮子。只需使用 .Promise.all
0赞 PranavKAndro 11/26/2019
在某些情况下,在不可能的情况下,它将是有帮助的。此外,默认情况下,错误处理由 forEach api 完成,因此没有问题。它照顾好了!
0赞 Bergi 11/26/2019
不,没有不可能的条件,但是/是。不,绝对不处理任何承诺错误。Promise.allasyncawaitforEach
8赞 gsaandy 12/2/2019 #17

只是在原来的答案上加一点

  • 原答案中的平行阅读语法有时令人困惑和难以阅读,也许我们可以用不同的方法编写它
async function printFiles() {
  const files = await getFilePaths();
  const fileReadPromises = [];

  const readAndLogFile = async filePath => {
    const contents = await fs.readFile(file, "utf8");
    console.log(contents);
    return contents;
  };

  files.forEach(file => {
    fileReadPromises.push(readAndLogFile(file));
  });

  await Promise.all(fileReadPromises);
}

  • 用于顺序操作,而不仅仅是...of,正常的 for 循环也将起作用
async function printFiles() {
  const files = await getFilePaths();

  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    const contents = await fs.readFile(file, "utf8");
    console.log(contents);
  }
}

7赞 lukaswilkeer 12/21/2019 #18

就像@Bergi的回应一样,但有一个区别。

Promise.all如果一个承诺被拒绝,则拒绝所有承诺。

因此,请使用递归。

const readFilesQueue = async (files, index = 0) {
    const contents = await fs.readFile(files[index], 'utf8')
    console.log(contents)

    return files.length <= index
        ? readFilesQueue(files, ++index)
        : files

}

const printFiles async = () => {
    const files = await getFilePaths();
    const printContents = await readFilesQueue(files)

    return printContents
}

printFiles()

聚苯乙烯

readFilesQueue在引起副作用*之外,最好是模拟、测试和/或监视,所以,有一个返回内容(sidenote)的函数并不酷。printFilesconsole.log

因此,代码可以简单地设计为:三个独立的函数,这些函数是“纯”的**,没有引入副作用,处理整个列表,并且可以很容易地修改以处理失败的情况。

const files = await getFilesPath()

const printFile = async (file) => {
    const content = await fs.readFile(file, 'utf8')
    console.log(content)
}

const readFiles = async = (files, index = 0) => {
    await printFile(files[index])

    return files.lengh <= index
        ? readFiles(files, ++index)
        : files
}

readFiles(files)

未来编辑/当前状态

Node 支持顶级 await(这还没有插件,不会有,可以通过 harmony 标志启用),它很酷,但不能解决一个问题(从战略上讲,我只在 LTS 版本上工作)。如何获取文件?

使用构图。给定代码,让我感觉到这是在模块内部,所以,应该有一个函数来做到这一点。如果没有,你应该使用 IIFE 将角色代码包装到一个异步函数中,创建一个简单的模块,为你做所有的事情,或者你可以采用正确的方式,有组合。

// more complex version with IIFE to a single module
(async (files) => readFiles(await files())(getFilesPath)

请注意,变量的名称会因语义而更改。传递一个函子(可由另一个函数调用的函数),并在内存上接收一个指针,该指针包含应用程序的初始逻辑块。

但是,如果不是模块,则需要导出逻辑?

将函数包装在异步函数中。

export const readFilesQueue = async () => {
    // ... to code goes here
}

或者更改变量的名称,随便什么......


*通过副作用,可以改变应用程序的状态/行为或引入应用程序中的错误(如 IO)的任何协同效应。

**通过“纯”,它是撇号,因为函数它不是纯的,当没有控制台输出,只有数据操作时,代码可以收敛到纯版本。

除此之外,为了纯粹起见,您需要使用处理副作用的单子,这些单子容易出错,并将该错误与应用程序分开处理。

30赞 Oliver Dixon 4/17/2020 #19

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

在 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 ...
})

评论

0赞 Ido Bleicher 10/14/2021
我认为如果你能完成这个例子会很有帮助:)在“如何使用”部分。就我而言:await asyncForEach(configuration.groupNames, async (groupName) => { await AddUsersToGroup(configuration, groupName);
0赞 JulienRioux 3/13/2022
谢谢,不错的解决方案!
0赞 Russo 11/1/2023
如果我需要返回一个 U 类型的数组怎么办?
9赞 richytong 5/21/2020 #20

您可以使用 ,但 async/await 不是那么兼容。这是因为从异步回调返回的 promise 期望被解析,但不会解析其回调执行中的任何 promise。因此,您可以使用 forEach,但您必须自己处理 promise 解析。Array.prototype.forEachArray.prototype.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.prototype.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)
}

评论

2赞 Mark Odey 5/30/2020
第一个 senario 非常适合需要在系列中运行的循环,而您不能用于
0赞 Wojciech Maj 11/19/2020 #21

如果要同时遍历所有元素:

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

如果你想非并发地遍历所有元素(例如,当你的映射函数有副作用时,或者一次对所有数组元素运行映射器会占用太多资源):

选项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/await

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

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

评论

0赞 Bergi 11/19/2020
选项 a 涉及 Promise 构造函数反模式
2赞 Adam Zerner 12/24/2020 #22

正如其他答案所提到的,您可能希望它按顺序执行,而不是并行执行。即。运行第一个文件,等待它完成,然后在完成后运行第二个文件。那不会发生。

我认为重要的是要解决为什么这种情况不会发生。

想想如何工作。我找不到来源,但我认为它的工作原理是这样的:forEach

const forEach = (arr, cb) => {
  for (let i = 0; i < arr.length; i++) {
    cb(arr[i]);
  }
};

现在想想当你做这样的事情时会发生什么:

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

在 的循环中,我们称之为 ,最终为 。该函数内部有一个,所以也许循环会等待这个然后再继续?forEachforcb(arr[i])logFile(file)logFileawaitforawaiti++

不,它不会。令人困惑的是,这不是工作方式。从文档中await

await 拆分执行流,允许异步函数的调用方恢复执行。在 await 延迟 async 函数的延续后,后续语句的执行将随之而来。如果此 await 是其函数执行的最后一个表达式,则通过向函数的调用方返回一个待处理的 Promise,以完成 await 的函数并恢复该调用方的执行,从而继续执行。

因此,如果您有以下情况,则之前不会记录这些数字:"b"

const delay = (ms) => {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
};

const logNumbers = async () => {
  console.log(1);
  await delay(2000);
  console.log(2);
  await delay(2000);
  console.log(3);
};

const main = () => {
  console.log("a");
  logNumbers();
  console.log("b");
};

main();

回旋回 ,就像 和 就像 。 不会因为某些 ing 而停止,也不会因为某些 ing 而停止。forEachforEachmainlogFilelogNumbersmainlogNumbersawaitforEachlogFileawait

20赞 yeah22 1/28/2021 #23

替换不起作用的 await 循环的简单插入式解决方案是替换为 并添加到开头。forEach()forEachmapPromise.all(

例如:

await y.forEach(async (x) => {

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

最后需要额外的费用。)

评论

1赞 srmark 10/7/2021
不完全一样。Promise.all 将同时运行所有 promise。for 循环是连续的。
2赞 Jellow 2/11/2021 #24

下面是在 forEach 循环中使用 async 的一个很好的示例。

编写自己的 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
   }
)
10赞 Johnz 4/2/2021 #25

从循环调用异步方法并不好。这是因为每个循环迭代都会延迟,直到整个异步操作完成。这不是很高性能。它还避免了 / 的并行化优势。asyncawait

更好的解决方案是一次创建所有 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);
}

评论

12赞 Bergi 4/2/2021
一次打开数千个文件以同时读取它们也不好。人们总是必须评估顺序、并行还是混合方法更好。顺序循环从根本上说并不是坏事,实际上首先使它们成为可能。此外,它们并不能“享受异步执行的好处”,因为您仍然可以一次运行多个这样的循环(例如,对 .awaitprintFiles
68赞 krupesh Anadkat 5/19/2021 #26

图片胜得1000字 - 仅供顺序法使用


背景:我昨晚也有类似的情况。我使用异步函数作为foreach参数。结果是不可预测的。当我对我的代码进行 3 次测试时,它运行 2 次没有问题,失败了 1 次。(有些奇怪)

最后,我头脑清醒,做了一些暂存垫测试。

方案 1 - 在 foreach 中使用异步可以变得多么不连续

enter image description here

const getPromise = (time) => { 
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`Promise resolved for ${time}s`)
    }, time)
  })
}

const main = async () => {
  const myPromiseArray = [getPromise(1000), getPromise(500), getPromise(3000)]
  console.log('Before For Each Loop')

  myPromiseArray.forEach(async (element, index) => {
    let result = await element;
    console.log(result);
  })

  console.log('After For Each Loop')
}

main();

方案 2 - 如上@Bergi建议使用循环for - of

enter image description here

const getPromise = (time) => { 
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`Promise resolved for ${time}s`)
    }, time)
  })
}

const main = async () => {
  const myPromiseArray = [getPromise(1000), getPromise(500), getPromise(3000)]
  console.log('Before For Each Loop')

  // AVOID USING THIS
  // myPromiseArray.forEach(async (element, index) => {
  //   let result = await element;
  //   console.log(result);
  // })

  // This works well
  for (const element of myPromiseArray) {
    let result = await element;
    console.log(result)
  }

  console.log('After For Each Loop')
}

main();

如果你像我一样是老派,你可以简单地使用经典的for循环,这太:)

const getPromise = (time) => { 
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`Promise resolved for ${time}s`)
    }, time)
  })
}

const main = async () => {
  const myPromiseArray = [getPromise(1000), getPromise(500), getPromise(3000)]
  console.log('Before For Each Loop')

  // AVOID USING THIS
  // myPromiseArray.forEach(async (element, index) => {
  //   let result = await element;
  //   console.log(result);
  // })

  // This works well too - the classic for loop :)
  for (let i = 0; i < myPromiseArray.length; i++) {
    const result = await myPromiseArray[i];
    console.log(result);
  }

  console.log('After For Each Loop')
}

main();

我希望这对某人有所帮助,美好的一天,干杯!

评论

9赞 krupesh Anadkat 5/19/2021
如果有人想知道 vscode 主题是什么 - 它是 github 的官方轻量级主题。如果有人用如此明亮的快照伤害了他们的眼睛,我深😅表歉意
0赞 close 3/2/2022
我建议使用短语“Before/After Loop”,当它不是“For Each Loop”时,它会不那么令人困惑。
0赞 User_coder 4/20/2022
兄弟在这里只是像一个绝对的异教徒一样使用 Githubs 官方编写代码。我什至没有生气。各有各的。尽管如此,我还是会缓存 to 加速该 for 循环并防止每次迭代之间的重新计算。length
2赞 theFreedomBanana 12/13/2022
部分失去了我的视线,但完全值得!
-3赞 jatinS 10/28/2021 #27

可以使用异步包中的 async.forEach 循环:

async.forEach(dataToLoop(array), async(data, cb) => {
                variable = await MongoQuery;
            }, function(err) {
                console.log(err);  
              })
            })
            .catch((err)=>{
              console.log(err);
            })
38赞 sam 11/10/2021 #28

@Bergi已经给出了如何正确处理这种特殊情况的答案。我就不在这里赘述了。

我想谈谈使用 and 循环之间的区别,当涉及到 和forEachforasyncawait

forEach 的工作原理

让我们看看它是如何工作的。根据 ECMAScript 规范,MDN 提供了一个可以用作 polyfill 的实现。我复制它并粘贴到这里并删除评论。forEach

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,因为它是用 声明的。里面,只是以正常的方式调用,如果回调本身返回一个 promise,javascript 引擎不会等待它被解析或拒绝。相反,它将 放入作业队列中,并继续执行循环。callbackasyncforEachcallbackpromise

回调等待fs.readFile(file, 'utf8')怎么样?

基本上,当你的异步有机会被执行时,js 引擎会暂停,直到被解析或拒绝,并在实现后恢复执行异步函数。因此,变量存储的是 的实际结果,而不是 。因此,注销文件内容而不是callbackfs.readFile(file, 'utf8')contentsfs.readFilepromiseconsole.log(contents)Promise

为什么......的作品

当我们编写一个通用循环时,我们获得了比 更多的控制权。让我们重构 .for offorEachprintFiles

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

当 evaluate 循环时,我们在函数内部有 promise,执行将暂停,直到 promise 结算。因此,您可以认为文件是按确定的顺序逐个读取的。forawaitasyncawait

按顺序执行

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

下面是一个示例:

const records = [1, 2, 3, 4];

async function saveRecord(record) {
  return new Promise((resolved, rejected) => {
    setTimeout(()=> {
      resolved(`record ${record} saved`)
    }, Math.random() * 500)
  });
}

async function forEachSaveRecords(records) {
  records.forEach(async (record) => {
    const res = await saveRecord(record);
    console.log(res);
  })
}

async function forofSaveRecords(records) {
  for (const record of records) {
    const res = await saveRecord(record);
    console.log(res);
  }
}
(async () => {
  console.log("=== for of save records ===")
  await forofSaveRecords(records)
  
  console.log("=== forEach save records ===")
  await forEachSaveRecords(records)
})()

我用来模拟将记录保存到数据库的过程 - 它是异步的,并且会花费随机时间。使用 ,记录以未确定的顺序保存,但使用 ,记录按顺序保存。setTimeoutforEachfor..of

评论

0赞 ado387 6/8/2022
简而言之:不以异步方式处理回调,因此无需等待。foreach
0赞 KAmit 8/28/2022
我感谢你的努力。我正在做一些傀儡师的事情,我想知道为什么我的异步 await 不起作用。你的回答澄清了我的疑问。与 foreach 一样,映射、过滤器等的问题也是相同的。
0赞 João Pimentel Ferreira 1/10/2022 #29

这不会使用 async/await 作为请求的 OP,并且仅在您在 NodeJS 的后端时才有效。虽然对某些人来说还是有帮助的,因为OP给出的例子是读取文件内容,而通常你在后台做文件读取。

完全异步和无阻塞:

const fs = require("fs")
const async = require("async")

const obj = {dev: "/dev.json", test: "/test.json", prod: "/prod.json"}
const configs = {}

async.forEachOf(obj, (value, key, callback) => {
    fs.readFile(__dirname + value, "utf8", (err, data) => {
        if (err) return callback(err)
        try {
            configs[key] = JSON.parse(data);
        } catch (e) {
            return callback(e)
        }
        callback()
    });
}, err => {
    if (err) console.error(err.message)
    // configs is now a map of JSON data
    doSomethingWith(configs)
})

评论

0赞 Bergi 1/10/2022
OP 从未要求不使用 /。他们说:“我正在尝试遍历一系列文件并等待每个文件的内容。asyncawait"
0赞 Bergi 1/10/2022
另外,为什么你说只在nodejs中工作?require("async").forEach
0赞 João Pimentel Ferreira 1/10/2022
@Bergi我明确表示 OP 并没有完全要求这一点,它只适用于 NodeJS。虽然对某些人来说还是有帮助的,因为OP给出的例子是读取文件内容,而通常你在后台做文件读取。
0赞 Bergi 1/10/2022
哦,我把这句话误解为“does (not use async/await) as the OP requested”而不是“does not (use async/await as the OP requested)”
7赞 Craig Hicks 1/18/2022 #30

OP的原始问题

在 forEach 循环中使用 async/await 有任何问题吗?...

在一定程度上涵盖了@Bergi选择的答案, 它展示了如何串行和并行处理。但是,并行性还存在其他问题:

  1. 命令 -- @chharvey 指出 -

例如,如果一个非常小的文件在一个非常大的文件之前完成读取,那么它将首先被记录下来,即使小文件位于 files 数组中的大文件之后。

  1. 可能一次打开太多文件 -- Bergi 在另一个答案下的评论

一次打开数千个文件以同时读取它们也不好。人们总是必须评估顺序、并行还是混合方法更好。

因此,让我们解决这些问题,展示简洁明了的实际代码,并且使用第三方库。易于剪切、粘贴和修改的东西。

并行读取(一次全部),串行打印(每个文件尽早打印)。

最简单的改进是执行完全并行,如 @Bergi 的回答所示,但进行一个小的更改,以便在保持顺序的同时尽快打印每个文件。

async function printFiles2() {
  const readProms = (await getFilePaths()).map((file) =>
    fs.readFile(file, "utf8")
  );
  await Promise.all([
    await Promise.all(readProms),                      // branch 1
    (async () => {                                     // branch 2
      for (const p of readProms) console.log(await p);
    })(),
  ]);
}

上面,两个单独的分支同时运行。

  • 分支 1:并行读取,一次全部读取,
  • 分支 2:连续读取以强制排序,但等待时间不超过必要的时间

这很容易。

与并发限制并行读取,串行打印(每个文件尽可能早)。

“并发限制”意味着同时读取的文件不超过。
就像一家一次只允许这么多顾客进来的商店(至少在 COVID 期间)。
N

首先引入一个辅助函数——

function bootablePromise(kickMe: () => Promise<any>) {
  let resolve: (value: unknown) => void = () => {};
  const promise = new Promise((res) => { resolve = res; });
  const boot = () => { resolve(kickMe()); };
  return { promise, boot };
}

该函数需要 函数作为参数来启动任务(在我们的例子中),但不会立即启动。bootablePromise(kickMe:() => Promise<any>)kickMereadFile

bootablePromise返回几个属性

  • promise类型Promise
  • bootof 类型函数()=>void

promise人生有两个阶段

  1. 承诺开始一项任务
  2. 作为一个承诺,完成一个它已经开始的任务。

promise调用时从第一种状态转换到第二种状态。boot()

bootablePromise用于printFiles --

async function printFiles4() {
  const files = await getFilePaths();
  const boots: (() => void)[] = [];
  const set: Set<Promise<{ pidx: number }>> = new Set<Promise<any>>();
  const bootableProms = files.map((file,pidx) => {
    const { promise, boot } = bootablePromise(() => fs.readFile(file, "utf8"));
    boots.push(boot);
    set.add(promise.then(() => ({ pidx })));
    return promise;
  });
  const concurLimit = 2;
  await Promise.all([
    (async () => {                                       // branch 1
      let idx = 0;
      boots.slice(0, concurLimit).forEach((b) => { b(); idx++; });
      while (idx<boots.length) {
        const { pidx } = await Promise.race([...set]);
        set.delete([...set][pidx]);
        boots[idx++]();
      }
    })(),
    (async () => {                                       // branch 2
      for (const p of bootableProms) console.log(await p);
    })(),
  ]);
}

和以前一样,有两个分支

  • 分支 1:用于运行和处理并发。
  • 分支 2:用于打印

现在的区别在于,只允许 Promise 并发运行。concurLimit

重要的变量是

  • boots:要调用的函数数组,以强制其对应的 Promise 进行转换。它仅在分支 1 中使用。
  • set:随机访问容器中有 Promise,因此一旦实现,就可以轻松删除它们。此容器仅在分支 1 中使用。
  • bootableProms:这些与最初 中的 Promise 相同,但它是一个数组而不是一个集合,并且数组永远不会改变。它仅在分支 2 中使用。set

使用模拟运行,其时间如下(文件名与时间(以毫秒为单位)。fs.readFile

const timeTable = {
  "1": 600,
  "2": 500,
  "3": 400,
  "4": 300,
  "5": 200,
  "6": 100,
};

可以看到这样的测试运行时间,表明并发正在工作 --

[1]0--0.601
[2]0--0.502
[3]0.503--0.904
[4]0.608--0.908
[5]0.905--1.105
[6]0.905--1.005

typescript playground 沙箱中作为可执行文件提供

114赞 Yilmaz 2/2/2022 #31
files.forEach(async (file) => {
    const contents = await fs.readFile(file, 'utf8')
})

问题是,迭代函数返回的 promise 被 忽略。 在每次异步代码执行完成后,不会等待移动到下一次迭代。所有功能 将在同一轮事件循环中被调用,这意味着它们是并行启动的,而不是按顺序启动的,并且在调用 forEach() 后立即继续执行,而不 等待所有操作完成。由于 forEach 不会等待每个 promise 解析,因此循环实际上在 promise 解析之前完成迭代。您期望在完成后,所有异步代码都已执行,但事实并非如此。您最终可能会尝试访问尚不可用的值。forEach()forEachfs.readFilefs.readFileforEach

您可以使用此示例代码测试行为

const array = [1, 2, 3];

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const delayedSquare = (num) => sleep(100).then(() => num * num);

const testForEach = (numbersArray) => {
  const store = [];
  // this code here treated as sync code
  numbersArray.forEach(async (num) => {
    const squaredNum = await delayedSquare(num);
    // this will console corrent squaredNum value
    // console.log(squaredNum) will log after console.log("store",store)
    console.log(squaredNum);
    store.push(squaredNum);
  });
  // you expect that store array is populated as [1,4,9] but it is not
  // this will return []
  console.log("store",store);
};
testForEach(array);
// Notice, when you test, first "store []" will be logged
// then squaredNum's inside forEach will log

解决方案是使用 for-of 循环。

for (const file of files){
    const contents = await fs.readFile(file, 'utf8')
}
-2赞 tenbits 4/6/2022 #32

在 2022 年,我仍然建议使用外部库来处理所有这些异步流。我已经为类似的事情创建了很多🔗模块。

您的示例是:

import fs from 'fs-promise'
import alot from 'alot'

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

    await alot(files)
        .forEachAsync(async file => {
            let content = await fs.readFile(file, 'utf8');
            console.log(content);
        })
        .toArrayAsync({ threads: 4 });
    }
}
printFiles()

对于简单的示例,异步肯定会完成这项工作,但是一旦任务更加复杂,您就必须为此使用一些实用程序。for..of

Alot 还有许多其他方法可以链接,例如 、 、 等。mapAsyncfilterAsyncgroupAsync

举个例子:

  • 使用 products meta 加载 JSON 文件
  • 提取ProductID
  • 从服务器加载产品
  • 筛选价格> 100 美元的人
  • 按价格升序排序
  • 排名前 50 位

import fs from 'fs-promise'
import alot from 'alot'
import axios from 'axios'
import { File } from 'atma-io'

let paths = await getFilePaths();
let products = await alot(paths)
    .mapAsync(async path => await File.readAsync<IProductMeta>(path))
    .mapAsync(async meta => await axios.get(`${server}/api/product/${meta.productId}`))
    .mapAsync(resp => resp.data)
    .filterAsync(product => product.price > 100)
    .sortBy(product => product.price, 'asc')
    .takeAsync(50)
    .toArrayAsync({ threads: 5, errors: 'include' });

评论

0赞 Bergi 4/6/2022
什么?JS 没有线程threads: 4
0赞 tenbits 4/7/2022
@Bergi 但是底层有。所有这些故事都意味着事件循环会一直等到它得到结果。通过定义我们设置并行启动的任务数量,其他任务将等待,直到至少在任务(fs、network、worker 等)上准备就绪。async\awaitthreads
-1赞 mikemaccana 12/12/2022 #33

对于 TypeScript 用户,具有工作类型的包装器Promise.all(array.map(iterator))

  • using 具有正确的类型,因为 TypeScript 的 stdlib 支持已经处理泛型。Promise.all(array.map(iterator))
  • 然而,每次需要异步映射时进行复制粘贴显然是次优的,并且不能很好地传达代码的意图 - 因此大多数开发人员会将其包装到包装器函数中。但是,执行此操作需要使用泛型来确保设置的值具有正确的类型。Promise.all(array.map(iterator))Promise.all(array.map(iterator))asyncMap()const value = await asyncMap()
export const asyncMap = async <ArrayItemType, IteratorReturnType>(
  array: Array<ArrayItemType>,
  iterator: (
    value: ArrayItemType,
    index?: number
  ) => Promise<IteratorReturnType>
): Promise<Array<IteratorReturnType>> => {
  return Promise.all(array.map(iterator));
};

快速测试:

it(`runs 3 items in parallel and returns results`, async () => {
  const result = await asyncMap([1, 2, 3], async (item: number) => {
    await sleep(item * 100);
    return `Finished ${item}`;
  });
  expect(result.length).toEqual(3);
  // Each item takes 100, 200 and 300ms
  // So restricting this test to 300ms plus some leeway
}, 320);

sleep()只是:

const sleep = async (timeInMs: number): Promise<void> => {
  return new Promise((resolve) => setTimeout(resolve, timeInMs));
};

评论

0赞 mikemaccana 12/31/2022
如果有人对这个答案有反馈,请告诉我 - 我通常认为大多数程序员不想复制粘贴,而只想有一个函数,可悲的是,没有泛型的包装不会有正确的类型。答案也不是重复的,应该对使用 async/await 和 TS 的任何人都有帮助,所以如果有什么我可以改进的地方(到目前为止的投票似乎有),请告诉我。Promise.all(array.map(iterator))Promise.all(array.map(iterator))
0赞 Beni Cherniavsky-Paskin 11/30/2023
我不确定这是 OP 想要的。它并行运行所有承诺,只在最后等待所有承诺完成(以不可预测的顺序)。有时这很好,有时你真的希望每次迭代在开始下一次迭代之前完成(包括)。await ...
4赞 Shubham Goel 9/4/2023 #34

你可以像这样使用一个简单的传统for循环

for(let i = 0; i< products.length; i++){
    await <perform some action like database read>
}

评论

0赞 Saad 9/5/2023
问题更多的是关于具体使用该方法。但是,如果您要以这种方式进行操作,我建议您改用循环。forEachfor..offor (const product of products) { await <perform some action like database read> }