通过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
。 每个async
callback函数调用都会返回一个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 .. of
和forEach + 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); } }