在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,所以我只想问问这是否有问题。
一个重要的警告是:等待+等待。。of方法和forEach+异步方式实际上有不同的效果。
在真正的for循环中等待将确保所有异步调用都逐一执行。forEach+异步方式将同时发出所有的承诺,这会更快,但有时会不堪重负(如果您执行一些DB查询或访问一些具有容量限制的web服务,并且不想一次发出100000个呼叫)。
如果您不使用async/await,并且希望确保一个接一个地读取文件,那么也可以使用reduce+promise(不太优雅)。
files.reduce((lastPromise, file) =>
lastPromise.then(() =>
fs.readFile(file, 'utf8')
), Promise.resolve()
)
或者您可以创建一个forEachAsync来帮助,但基本上使用相同的for循环底层。
Array.prototype.forEachAsync = async function(cb){
for(let x of this){
await cb(x);
}
}
当然,代码确实有效,但我很确定它并没有达到您期望的效果。它只是触发多个异步调用,但printFiles函数在这之后会立即返回。
按顺序读取
如果您想按顺序读取文件,则实际上不能使用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。每一个异步回调函数调用都会返回一个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)
}));
}
在一个文件中弹出几个方法,以串行化的顺序处理异步数据,并为代码提供更传统的风格,这是非常轻松的。例如:
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));
}
一个重要的警告是:等待+等待。。of方法和forEach+异步方式实际上有不同的效果。
在真正的for循环中等待将确保所有异步调用都逐一执行。forEach+异步方式将同时发出所有的承诺,这会更快,但有时会不堪重负(如果您执行一些DB查询或访问一些具有容量限制的web服务,并且不想一次发出100000个呼叫)。
如果您不使用async/await,并且希望确保一个接一个地读取文件,那么也可以使用reduce+promise(不太优雅)。
files.reduce((lastPromise, file) =>
lastPromise.then(() =>
fs.readFile(file, 'utf8')
), Promise.resolve()
)
或者您可以创建一个forEachAsync来帮助,但基本上使用相同的for循环底层。
Array.prototype.forEachAsync = async function(cb){
for(let x of this){
await cb(x);
}
}