通过forEach循环使用async / await

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 ,所以我只是想问一下是否有任何问题。

当然代码确实可行,但我很确定它没有达到你期望的效果。 它只是触发多个asynchronous调用,但printFiles函数在此之后立即返回。

如果forEach顺序读取文件, 确实无法使用forEach 。 只需使用一个现代for … of循环,其中await将按预期工作:

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

如果你想并行读取文件, 确实不能使用forEach 。 每个asynccallback函数调用都会返回一个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) })); } 

对我来说,使用Promise.all()map()有点难以理解和冗长,但是如果你想用普通的JS来做,那么我猜你就是最好的select。

如果您不介意添加模块,那么我实现了数组迭代方法,以便可以以非常简单的方式使用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); }); } printFiles() 

对迭代

在一个能处理序列化顺序的asynchronous数据的文件中popup几个方法,给你的代码提供一个更传统的风格是非常容易的。 例如:

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

上面的两个解决scheme都可以工作,但是Antonio的工作是用更less的代码来完成的,下面是它如何帮助我从数据库中parsing数据,从几个不同的子引用中parsing数据,然后将它们全部parsing成数组,完成:

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

一个重要的注意事项是:方法的await + for .. offorEach + async方式实际上有不同的效果。

await一个真正for循环将确保所有的asynchronous调用一个接一个地执行。 而forEach + async方式将同时触发所有的承诺,但速度更快,但有时会不堪重负( 如果您进行一些数据库查询或访问某些具有数量限制的Web服务,并且不希望一次触发100,000个呼叫)。

如果你不使用async/await并且想要确保文件被一个接一个地读取,你也可以使用reduce + promise (不那么优雅)。

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

或者你可以创build一个forEachAsync来帮助,但基本上使用相同的循环底层。

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