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

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

时间:

代码确实有效,但是,我很确定它不会像你期望的那样做,它只是触发多个异步调用,函数在执行之后,立即返回。

如果要按顺序读取文件,则不能使用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获得的Promise数组:


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


}



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


 });


})();



下面是一些forEachAsync Prototype ,注意你需要对它们进行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));


}



请注意,虽然您可以在自己的代码中包含它,但是,不应将其包含在您分发给其他人的库中(以避免污染其全局变量)。


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



使用Task , futurize , 和a traversable List,你只需执行以下操作,


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



我没有在控制台上尝试这个代码,可能有一些错误,:-p

目前,Array.forEach原型属性不支持异步操作,但是,我们可以创建自己的poly-fill来满足我们的需求。


// 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方法,

我们来测试一下。


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



..等等 : )

需要注意的事项:

  • 你的iteratorFunction必须是异步函数或
  • 之前创建的任何数组 Array.prototype.<yourAsyncFunc> = <yourAsyncFunc> 将不具备此功能
...