在forEach循环中使用异步/等待

心不动则不痛 提交于 2019-12-17 09:54:02

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

forEach循环中使用async / await是否有任何问题? 我正在尝试遍历文件数组并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 ,所以我只想问一下这是否有问题。


#1楼

确保代码确实有效,但是我很确定它不会实现您期望的功能。 它只是触发多个异步调用,但是在printFiles之后printFiles函数确实会立即返回。

如果forEach顺序读取文件, 实际上不能使用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 ,您可以等待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)
  }));
}

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

#3楼

上面的两个解决方案都可以工作,但是,安东尼奥用更少的代码来完成这项工作,这就是它如何帮助我从数据库中解析数据,来自几个不同的子引用,然后将它们全部推入数组并以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)))

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

#5楼

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

在真实的for循环中await将确保所有异步调用都被一个接一个地执行。 而且forEach + async方式将同时触发所有的Promise,这更快但有时会不知所措( 如果您进行一些数据库查询或访问一些具有音量限制的Web服务,并且不想一次触发100,000个调用)。

如果您不使用async/await并想确保文件被一个又一个地读取,则也可以使用reduce + promise (不太优雅)。

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);
    }
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!