Using async/await with a forEach loop
在
1 2 3 4 5 6 7 8 9 10 11 12 | 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() |
这段代码确实有效,但这会出什么问题吗?我有人告诉我,你不应该在这样的高阶函数中使用
当然,代码确实有效,但我很确定它不会像您期望的那样工作。它只会触发多个异步调用,但之后
如果要按顺序读取文件,则不能使用
1 2 3 4 5 6 7 8 | async function printFiles () { const files = await getFilePaths(); for (const file of files) { const contents = await fs.readFile(file, 'utf8'); console.log(contents); } } |
如果要并行读取文件,则不能使用
1 2 3 4 5 6 7 8 | async function printFiles () { const files = await getFilePaths(); await Promise.all(files.map(async (file) => { const contents = await fs.readFile(file, 'utf8') console.log(contents) })); } |
你是如此es2018,可以大大简化在to of the above answers to:P></
1 2 3 4 5 6 7 | async function printFiles () { const files = await getFilePaths() for await (const file of fs.readFile(file, 'utf8')) { console.log(contents) } } |
规格:EEA HTTPS:tc39 /建议/ / / github.com -异步迭代P></
2018年09月:this has been 10答案让很多关注recently,Please see for further rauschmayer'阿克塞尔的博客信息about异步迭代:http://///asynchronous-iteration.html 2ality.com 2016年10P></
在conjunction instead of
1 2 3 4 5 6 7 8 9 10 11 12 | 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()); } |
NPM上的P迭代模块实现了数组迭代方法,因此它们可以以非常简单的方式用于异步/等待。
举个例子:
1 2 3 4 5 6 7 8 9 10 11 | 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); }); })(); |
here are some
1 2 3 4 5 6 7 | 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)); } |
这可能包括你我的笔记在你自己的代码,你应该包括在你distribute not to this图书馆人(to their avoid polluting全局变量)。P></
在一个文件中弹出两个方法,将以序列化的顺序处理异步数据,并为代码提供一种更传统的风格,这是非常简单的。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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"中,您可以在相邻文件中执行类似于下面的操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | ... /* 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)); } |
然而,上述两种解决方案都起作用,Antonio的解决方案使用更少的代码来完成这项工作,下面介绍了它如何帮助我从数据库、几个不同的子引用中解析数据,然后将它们全部推送到一个数组中,并在完成所有操作后按承诺进行解析:
1 2 3 4 5 6 7 8 9 | 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))) |
array.foreach currently the prototype属性不支持异步操作,我们可以创造我们自己的,但让我们聚到满足需求。P></
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 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安method that are to后arrays定义这些操作。P></
让我们测试它。P></
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | // 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 |
我们可以给the same for some of the other阵列映射函数的类。P></
1 2 3 4 5 6 7 8 9 10 11 | 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 |
……和我在线:)P></
注释:some things toP></
- 你iteratorfunction must be an异步函数或承诺
- 在任何arrays created this will have not available
Array.prototype. 特征=
使用task、futureize和可遍历列表,您可以简单地
1 2 3 4 5 6 | async function printFiles() { const files = await getFiles(); List(files).traverse( Task.of, f => readFile( f, 'utf-8')) .fork( console.error, console.log) } |
这是你如何设置的
1 2 3 4 5 6 7 | 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) |
构造所需代码的另一种方法是
1 2 3 | const printFiles = files => List(files).traverse( Task.of, fn => readFile( fn, 'utf-8')) .fork( console.error, console.log) |
或者更注重功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 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) |
然后从父函数
1 2 3 4 5 | async function main() { /* awesome code with side-effects before */ printFiles( await getFiles() ); /* awesome code with side-effects after */ } |
如果您真的想要更灵活的编码,您可以这样做(为了好玩,我正在使用提议的管道转发操作符)
1 2 3 4 5 6 7 8 | import { curry, flip } from 'ramda' export const readFile = fs.readFile |> future, |> curry, |> flip export const readFileUtf8 = readFile('utf-8') |
PS-我没有在控制台上尝试此代码,可能有一些拼写错误…"直接的自由式,从穹顶上下来!"正如90年代的孩子们所说。-P
除了@bergi的回答,我还想提供第三种选择。这与@bergi的第二个例子非常相似,但不是单独等待每一个
1 2 3 4 5 6 7 8 9 10 | 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); } |
注意,传递给
在@bergi的回答中,控制台可能会将文件内容记录得不正常。例如,如果一个非常小的文件在一个非常大的文件之前完成读取,那么它将首先被记录,即使这个小文件在
一个重要的警告是:
在实际的
如果您不使用
1 2 3 4 5 | files.reduce((lastPromise, file) => lastPromise.then(() => fs.readFile(file, 'utf8') ), Promise.resolve() ) |
或者您可以创建一个foreachasync来帮助您,但基本上使用相同的for循环底层。
1 2 3 4 5 | Array.prototype.forEachAsync = async function(cb){ for(let x of this){ await cb(x); } } |
val' similar to s
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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(); |
alternatively method has a,静电
1 2 3 4 5 6 7 8 9 10 11 12 | 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(); |
不管一个人多优势,the main of the Library is that you can do something like to链:异步方法P></
1 2 3 4 5 6 7 8 | 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(); |
我将使用测试良好的(每周数百万次下载)PIFY和异步模块。如果您不熟悉异步模块,我强烈建议您查看它的文档。我见过多个devs浪费时间重新创建其方法,或者更糟,当高阶异步方法简化代码时,很难维护异步代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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 ``` |