JavaScript+Firebase时序问题:final数组存入Firestore集合时为空
解决Firestore异步操作导致数组为空的时序问题
这个问题我太熟了!就是典型的异步操作未完成就提前返回数据的问题,控制台那个[] -> value is just evaluated now的警告其实已经给你敲警钟了——你打印final的时候它确实是空数组,只是后来异步请求完成后数组内容被更新了,而控制台保存了数组的引用,展开时才会读取最新值。
问题根源分析
你的代码里,在querySnapshot.forEach循环中,每个文档都会触发一个db2.collection("list").where(...).get()的异步请求,但你没有等待这些请求全部完成,就直接调用了resolve(final)。这就导致final数组还没来得及被填充数据,就被返回给了add函数,自然会在Firestore里存入空数组。另外用counter给数组赋值的方式也有风险,因为异步操作的完成顺序不一定和循环顺序一致,容易出现索引错位。
修复后的代码
我帮你重构了代码,用Promise.all和async/await来确保所有异步操作完成后再返回最终数组:
首先,确保Date有addDays方法(如果没有的话先实现):
// 给Date原型添加addDays方法,确保addedDate能正确计算 Date.prototype.addDays = function(days) { const date = new Date(this.valueOf()); date.setDate(date.getDate() + days); return date; };
然后修改test函数:
function test() { let today = new Date(); let addedDate = new Date(today.addDays(7)); // 直接返回Firestore查询的Promise,不用额外封装一层Promise return db.collection("email").get() .then((querySnapshot) => { if (querySnapshot.empty) { return []; // 没有邮箱数据时直接返回空数组 } // 用map收集每个文档处理的Promise,方便后续统一等待 const documentTasks = querySnapshot.docs.map(async (data) => { const emailData = data.data(); if (!emailData) return null; // 跳过无数据的文档 // 等待子集合的查询完成 const listSnapshot = await db.collection("email").doc(data.id) .collection("list") .where("expiaryDate", "<", addedDate.getTime()) .get(); if (listSnapshot.empty) return null; // 跳过无过期食物的邮箱 // 转换食物数据格式 const foodArr = listSnapshot.docs.map(food => { const foodData = food.data(); return { name: foodData.name, time: foodData.expiaryDate }; }); // 返回组装好的邮箱+食物对象 return { email: emailData.email, food: foodArr }; }); // 等待所有文档的异步任务完成,过滤掉空值 return Promise.all(documentTasks).then(results => results.filter(item => item !== null)); }) .catch(err => { console.error("查询出错:", err); throw err; // 抛出错误让调用方处理 }); }
最后简化add函数:
async function add() { try { const finalData = await test(); console.log("最终要存入的数据:", finalData); const docRef = await db.collection("sendGrid").add({ response: finalData }); console.log("数据已成功存入,文档ID:", docRef.id); } catch (err) { console.error("存入数据出错:", err); } }
关键修复点
- 用
querySnapshot.docs.map替代forEach:这样可以把每个文档的异步处理逻辑包装成Promise,存入数组后用Promise.all等待全部完成。 - 用
async/await简化异步逻辑:避免了多层then嵌套,代码更易读,也能确保子集合查询完成后再处理数据。 - 去掉
counter索引:改用map和filter生成最终数组,避免异步顺序导致的索引错误。 - 直接返回Firestore查询的Promise:原代码里额外封装的
adderPromise是多余的,Firestore的get()本身就返回Promise。
这样修改后,finalData会在所有异步查询完成后才被返回,存入Firestore的就是完整的数组了。
内容的提问来源于stack exchange,提问作者Lee Hwan
相关产品推荐
相关产品推荐

