在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,所以我只想问问这是否有问题。
只是在原有答案的基础上
原始答案中的平行阅读语法有时令人困惑且难以理解,也许我们可以用不同的方法来编写
async function printFiles() {
const files = await getFilePaths();
const fileReadPromises = [];
const readAndLogFile = async filePath => {
const contents = await fs.readFile(file, "utf8");
console.log(contents);
return contents;
};
files.forEach(file => {
fileReadPromises.push(readAndLogFile(file));
});
await Promise.all(fileReadPromises);
}
用于顺序操作,而不仅仅用于。。。的,循环的正常值也将起作用
async function printFiles() {
const files = await getFilePaths();
for (let i = 0; i < files.length; i++) {
const file = files[i];
const contents = await fs.readFile(file, "utf8");
console.log(contents);
}
}
除了@Bergi的回答,我想提供第三种选择。这与@Bergi的第二个例子非常相似,但不是单独等待每个readFile,而是创建一个承诺数组,每个承诺都在最后等待。
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);
}
注意,传递给.map()的函数不需要是异步的,因为fs.readFile无论如何都会返回Promise对象。因此,Promise是Promise对象的数组,可以将其发送到Promise.all()。
在@Bergi的回答中,控制台可以按照文件内容的读取顺序记录文件内容。例如,如果一个非常小的文件在一个非常大的文件之前完成了读取,那么它将首先被记录,即使小文件在文件数组中位于大文件之后。然而,在我上面的方法中,您可以保证控制台将以与提供的阵列相同的顺序记录文件。
对于TypeScript用户,使用工作类型的Promise.all(array.map(迭代器))包装器
使用Promise.all(array.map(迭代器))具有正确的类型,因为TypeScript的stdlib支持已经处理了泛型。然而,每次需要异步映射时复制粘贴Promise.all(array.map(迭代器))显然不是最佳的,Promise.all(array.ma(迭代))并不能很好地传达代码的意图,因此大多数开发人员都会将其包装成一个asyncMap()包装函数。然而,要做到这一点,需要使用泛型来确保使用const value=await asyncMap()设置的值具有正确的类型。
export const asyncMap = async <ArrayItemType, IteratorReturnType>(
array: Array<ArrayItemType>,
iterator: (
value: ArrayItemType,
index?: number
) => Promise<IteratorReturnType>
): Promise<Array<IteratorReturnType>> => {
return Promise.all(array.map(iterator));
};
快速测试:
it(`runs 3 items in parallel and returns results`, async () => {
const result = await asyncMap([1, 2, 3], async (item: number) => {
await sleep(item * 100);
return `Finished ${item}`;
});
expect(result.length).toEqual(3);
// Each item takes 100, 200 and 300ms
// So restricting this test to 300ms plus some leeway
}, 320);
sleep()只是:
const sleep = async (timeInMs: number): Promise<void> => {
return new Promise((resolve) => setTimeout(resolve, timeInMs));
};
当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