在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,所以我只想问问这是否有问题。
对于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));
};
当然,代码确实有效,但我很确定它并没有达到您期望的效果。它只是触发多个异步调用,但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)
}));
}
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')
}
这里是一个在forEach循环中使用异步的好例子。
编写自己的asyncForEach
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
你可以这样用
await asyncForEach(array, async function(item,index,array){
//await here
}
)
与Antonio Val的p迭代类似,另一种npm模块是异步af:
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();
或者,async-af有一个静态方法(log/logAF)来记录promise的结果:
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();
然而,该库的主要优点是您可以链接异步方法来执行以下操作:
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();
异步af