在foreach循环中使用async/await

euoag5mw  于 2021-10-10  发布在  Java
关注(0)|答案(19)|浏览(498)

使用时有什么问题吗 async / await 在一个 forEach 环我正在尝试循环浏览一系列文件和 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 在像这样的高阶函数中,我只是想问一下这是否有什么问题。

f0brbegy

f0brbegy1#

今天我遇到了多种解决方案。在foreach循环中运行异步等待函数。通过构建 Package 器,我们可以实现这一点。
更详细地解释它是如何工作的

jgwigjjp

jgwigjjp2#

目前array.foreach prototype属性不支持异步操作,但我们可以创建自己的多边形填充以满足需要。

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

就这样!现在,在这些to操作之后定义的任何数组上都可以使用async 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

... 等等:)
需要注意的一些事项:
迭代器函数必须是异步函数或承诺
以前创建的任何数组 Array.prototype.<yourAsyncFunc> = <yourAsyncFunc> 将没有此功能可用

4urapxun

4urapxun3#

使用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 */
}

如果你真的想在编码上有更多的灵活性,你可以这样做(为了好玩,我正在使用建议的管道转发操作符)

import { curry, flip } from 'ramda'

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

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

ps-我没有在控制台上尝试此代码,可能有一些输入错误…”笔直的自由泳,从圆顶上跳下!”正如90年代的孩子们所说:-P

s71maibg

s71maibg4#

就像@bergi的React,但有一点不同。 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 不在 printFiles 导致副作用*由 console.log ,最好是模拟、测试和/或间谍,因此,使用返回内容的函数(sidenote)并不酷。
因此,代码可以简单地设计为:三个独立的函数,它们是“纯的”**并且没有副作用,处理整个列表,并且可以很容易地修改以处理失败的情况。

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支持顶级wait(它还没有插件,没有,可以通过harmony标志启用),它很酷,但没有解决一个问题(策略上我只在lts版本上工作)。如何获取文件?
使用构图。给定代码,让我感觉这是在一个模块内,所以,应该有一个函数来完成它。如果没有,您应该使用iife将角色代码 Package 到一个异步函数中,创建一个简单的模块来为您完成所有工作,或者您可以使用正确的方法,即组合。

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

请注意,变量的名称会因语义而更改。您传递一个函子(一个可由另一个函数调用的函数),并在包含应用程序初始逻辑块的内存上接收指针。
但是,如果不是模块,您需要导出逻辑?
将函数 Package 为异步函数。

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

或者改变变量的名称,不管怎样。。。 * 副作用指的是应用程序可能改变状态/行为或在应用程序中引入错误(如io)的任何协同效应。 ** “pure”是用撇号表示的,因为它不是纯函数,代码可以聚合到纯版本,而没有控制台输出,只有数据操作。
除此之外,纯粹地说,您需要使用处理副作用的单子,这些单子容易出错,并且将错误与应用程序分开处理。

eblbsuwk

eblbsuwk5#

一种简单的插入式解决方案,用于替换 forEach() 正在替换不工作的等待循环 forEach 具有 map 及加入 Promise.all( 从头开始。
例如: await y.forEach(async (x) => {await Promise.all(y.map(async (x) => { 额外的 ) 最后需要的是。

7vux5j2d

7vux5j2d6#

从循环中调用异步方法是不好的。这是因为每个循环迭代将被延迟,直到整个异步操作完成。这不是很有效。它还避免了并行化的优点 async / await .
更好的解决方案是一次创建所有承诺,然后使用 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);
}
zyfwsgd6

zyfwsgd67#

当然,代码确实有效,但我非常确定它没有达到您期望的效果。它只是触发多个异步调用,但是 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 回调函数调用确实会返回一个承诺,但您将它们扔掉,而不是等待它们。只用 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)
  }));
}
cmssoen2

cmssoen28#

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

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

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

参见规范:提案异步迭代
2018-09-10:这个答案最近引起了很多关注,有关异步迭代的更多信息,请参阅axel rauschmayer的博客文章。

bpsygsoo

bpsygsoo9#

而不是 Promise.all 结合 Array.prototype.map (这并不保证 Promise 解决了),我使用 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());
}
x6492ojm

x6492ojm10#

npm上的p-iteration模块实现了数组迭代方法,因此它们可以以非常简单的方式与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);
  });
})();
vwoqyblh

vwoqyblh11#

这里有一些 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));
}

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

5sxhfpxr

5sxhfpxr12#

除了@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);
}

请注意,函数传递给 .map() 不需要 async 自从 fs.readFile 仍然返回承诺对象。所以 promises 是promise对象的数组,可以发送到 Promise.all() .
在@bergi的回答中,控制台可能会按照读取顺序记录文件内容。例如,如果一个非常小的文件在一个非常大的文件之前完成了读取,那么它将首先被记录,即使小文件位于数据库中的大文件之后 files 数组。但是,在我上面的方法中,可以保证控制台将按照与提供的数组相同的顺序记录文件。

1sbrub3j

1sbrub3j13#

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

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

xcitsw8814#

图片价值1000字-仅用于顺序方法

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

场景1-在foreach中使用async可以实现多大程度的非连续性

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循环

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

我希望这对某人有帮助,祝你好运,干杯!

fjaof16o

fjaof16o15#

一个重要的警告是: await + for .. of 方法和步骤 forEach + async 这种方式实际上有不同的效果。
await 在一个真实的世界里 for 循环将确保逐个执行所有异步调用。和 forEach + async way将同时发出所有承诺,这会更快,但有时会让人不知所措(如果您执行一些db查询或访问一些有音量限制的web服务,并且不希望一次发出100000个呼叫)。
你也可以使用 reduce + promise (不那么优雅)如果你不使用 async/await 并希望确保一个接一个地读取文件。

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

也可以创建foreachasync来提供帮助,但基本上对底层循环使用相同的方法。

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

相关问题