在Firebase查询结果的forEach循环中正确使用async/await
解决forEach无法等待异步操作导致的Firebase数据查询问题
你遇到的核心问题是:forEach循环不会等待内部的异步函数执行完成。虽然你给forEach的回调加了async,但forEach本身不会处理这些回调返回的Promise,会直接继续执行后续代码,导致"USER TEST ADDED FOR GRAPH 3"先打印,而userTests数组还没填满就返回给前端了。
下面提供两种可靠的解决方法:
方法一:使用for...of循环(顺序执行)
for...of可以和await完美配合,会逐个等待每个异步操作完成后再进行下一次循环,保证执行顺序和数据填充的正确性:
async function getUserTestGraph(req, res) { const userGraphId = req.query.id; const userGraph = {}; const userTests = []; const snapshot = await db.collection('userGraphs').where('id', '==', userGraphId).get(); if (snapshot.empty) { console.log('No matching user Graph found'); return res.status(200).json({ graphStatus: 200, message: "No user graph found!" }); } const graphDoc = snapshot.docs[0].data(); userGraph.id = graphDoc.id; userGraph.userId = graphDoc.userId; userGraph.diseaseName = graphDoc.diseaseName; userGraph.userDiseaseId = graphDoc.userDiseaseId; // 获取关联的测试列表 const snapshotTests = await db.collection('userGraphTestSet').where('testSetId', '==', userGraphId).get(); // 用for...of替代forEach for (const test of snapshotTests.docs) { const testData = test.data(); const userTest = { id: testData.id, testSetId: testData.testSetId, userId: testData.userId, userTestId: testData.userTestId, testName: testData.testName }; // 查询测试详细信息 const snapshotTestInfo = await db.collection('userTests').where('id', '==', testData.userTestId).get(); if (!snapshotTestInfo.empty) { const testInfo = snapshotTestInfo.docs[0].data(); userTest.testAmount = testInfo.testAmount; userTest.testUnit = testInfo.testUnitId; userTest.testDate = testInfo.testDate; } console.log("USER TEST ADDED FOR GRAPH 1"); console.log(userTest); userTests.push(userTest); console.log("USER TEST ADDED FOR GRAPH 2"); console.log(userTests); } console.log("USER TEST ADDED FOR GRAPH 3"); console.log(userTests); return res.status(200).json({ graphStatus: 200, userGraph: userGraph, userTests: userTests }); }
方法二:使用Promise.all + map(并行执行,效率更高)
如果不需要严格的顺序执行,用map把每个测试项的异步操作转换成Promise,再用Promise.all等待所有Promise完成,这样所有查询可以并行进行,提升性能:
async function getUserTestGraph(req, res) { const userGraphId = req.query.id; const userGraph = {}; const snapshot = await db.collection('userGraphs').where('id', '==', userGraphId).get(); if (snapshot.empty) { console.log('No matching user Graph found'); return res.status(200).json({ graphStatus: 200, message: "No user graph found!" }); } const graphDoc = snapshot.docs[0].data(); userGraph.id = graphDoc.id; userGraph.userId = graphDoc.userId; userGraph.diseaseName = graphDoc.diseaseName; userGraph.userDiseaseId = graphDoc.userDiseaseId; // 获取关联的测试列表 const snapshotTests = await db.collection('userGraphTestSet').where('testSetId', '==', userGraphId).get(); // 用map生成所有异步操作的Promise数组 const userTestPromises = snapshotTests.docs.map(async (test) => { const testData = test.data(); const userTest = { id: testData.id, testSetId: testData.testSetId, userId: testData.userId, userTestId: testData.userTestId, testName: testData.testName }; // 查询测试详细信息 const snapshotTestInfo = await db.collection('userTests').where('id', '==', testData.userTestId).get(); if (!snapshotTestInfo.empty) { const testInfo = snapshotTestInfo.docs[0].data(); userTest.testAmount = testInfo.testAmount; userTest.testUnit = testInfo.testUnitId; userTest.testDate = testInfo.testDate; } console.log("USER TEST ADDED FOR GRAPH 1"); console.log(userTest); return userTest; }); // 等待所有Promise完成,得到完整的userTests数组 const userTests = await Promise.all(userTestPromises); console.log("USER TEST ADDED FOR GRAPH 3"); console.log(userTests); return res.status(200).json({ graphStatus: 200, userGraph: userGraph, userTests: userTests }); }
为什么forEach不行?
forEach的设计就是遍历执行回调,它不会关心回调是否是异步函数,也不会等待回调返回的Promise完成。你写的await snapshotTests.forEach(...)其实没有任何效果,因为forEach本身返回undefined,await undefined会直接resolve,不会等待内部的异步操作。
内容的提问来源于stack exchange,提问作者MattJ
相关产品推荐
相关产品推荐

