在forEach循环中使用async/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,所以我只想问问这是否有问题。
files.forEach(异步(文件)=>{const contents=await fs.readFile(文件,'utf8')})
问题是,forEach()忽略了迭代函数返回的promise。在每个异步代码执行完成后,forEach不会等待移动到下一个迭代。所有fs.readFile函数将在同一轮事件循环中调用,这意味着它们是并行启动的,而不是顺序启动的,并且在调用forEach()后立即继续执行等待所有fs.readFile操作完成。由于forEach不等待每个promise解析,因此循环实际上在解析promise之前完成了迭代。您希望在forEach完成后,所有异步代码都已执行,但事实并非如此。您最终可能会尝试访问尚未可用的值。
您可以使用以下示例代码测试行为
常量数组=[1,2,3];const sleep=(ms)=>new Promise(解析=>setTimeout(解析,ms));常量delayedSquare=(num)=>睡眠(100)。然后(()=>num*num);const testForEach=(numbersArray)=>{常量存储=[];//此处将此代码视为同步代码numbersArray.forEach(异步(num)=>{const squaredNum=等待延迟平方(num);//这将控制相应的squaredNum值console.log(平方数);store.push(平方数);});//您希望存储阵列已填充,但未填充//这将返回[]console.log(“存储”,存储);};testForEach(数组);//注意,当您测试时,将记录第一个“store[]”//然后squaredNum的内部forEach将记录
解决方案是使用for of循环。
for (const file of files){
const contents = await fs.readFile(file, 'utf8')
}
这不会像OP请求的那样使用async/await,只有当您在NodeJS的后端时才有效。尽管这对某些人来说可能还是有帮助的,因为OP给出的示例是读取文件内容,通常在后端进行文件读取。
完全异步和非阻塞:
const fs = require("fs")
const async = require("async")
const obj = {dev: "/dev.json", test: "/test.json", prod: "/prod.json"}
const configs = {}
async.forEachOf(obj, (value, key, callback) => {
fs.readFile(__dirname + value, "utf8", (err, data) => {
if (err) return callback(err)
try {
configs[key] = JSON.parse(data);
} catch (e) {
return callback(e)
}
callback()
});
}, err => {
if (err) console.error(err.message)
// configs is now a map of JSON data
doSomethingWith(configs)
})
目前,Array.forEach原型属性不支持异步操作,但我们可以创建自己的多边形填充来满足我们的需要。
// 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}
就这样!现在,在这些操作之后定义的任何数组上都可以使用asyncforEach方法。
让我们测试一下。。。
// 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
…等等:)
需要注意的一些事项:
迭代器函数必须是异步函数或promise在Array.protocol.<yourAsyncFunc>=<yourAsync Func>之前创建的任何数组都不具有此功能
当fs基于承诺时,Bergi的解决方案非常有效。您可以使用bluebird、fs extra或fs promise。
然而,节点的本地fs库的解决方案如下:
const result = await Promise.all(filePaths
.map( async filePath => {
const fileContents = await getAssetFromCache(filePath, async function() {
// 1. Wrap with Promise
// 2. Return the result of the Promise
return await new Promise((res, rej) => {
fs.readFile(filePath, 'utf8', function(err, data) {
if (data) {
res(data);
}
});
});
});
return fileContents;
}));
注:require('fs')强制将函数作为第三个参数,否则抛出错误:
TypeError [ERR_INVALID_CALLBACK]: Callback must be a function