TypeScript+Firebase Functions中forEach不循环的问题排查
问题描述
我需要将Google Firebase Realtime Database的DataSnapshot转换为自定义对象数组,每个对象对应一个子节点及其所有子元素,且返回前要完成异步处理。参考StackOverflow方案写了代码后遇到两个问题:
forEach循环仅执行一次,即便snap序列化后的JSON显示存在多个有效子节点- 返回的数组为
[{}]空对象结构
原实现代码
export async function unpack_snap(snap: any): Promise<(CustomObject[] | null)>{ const toWait: any[] = []; console.log(JSON.stringify(snap)); await snap.forEach(async obj => { toWait.push(obj_from_snap((obj))); // 将Promise存入数组 console.log(obj.key); // 验证循环是否执行 }); await Promise.all(toWait); return toWait; } export async function obj_from_snap(co: DataSnapshot) : Promise<(CustomObject | null)>{ var newCO: CustomObject = { val1 : co.val().val1, val2 : co.val().val2, Data : await GetData(co.key) }; console.log(`Data: ${newCO.Data}`) return newCO; } export async function getData(key: String): Promise<(String | null)>{ ... }
验证测试
我单独写了仅打印key的代码,结果依旧只打印第一个子节点的key后函数就退出返回null:
await snap.forEach(async obj => { console.log(obj.key); }); return null;
想明确两个问题:
- 循环为何只执行一次?
- 返回
toWait数组能否获取所有CustomObject?若不行,有没有更优的实现方式?
原因分析与解决方案
核心问题:Firebase的forEach不兼容异步回调
Firebase Realtime Database的DataSnapshot.forEach()方法不支持异步函数作为回调参数。它会在回调返回Promise的瞬间就判定当前循环步骤完成,甚至直接终止整个循环——这就是你只看到第一个节点日志的根本原因。
另外,你当前的toWait数组存储的是未解析的Promise对象,直接返回它拿到的是Promise数组,而非解析后的CustomObject数组,这也是返回[{}]的原因。
正确实现方式
绕过异步回调的问题,先将DataSnapshot转换为普通子节点数组,再用map生成Promise数组,最后通过Promise.all统一解析:
export async function unpack_snap(snap: DataSnapshot): Promise<CustomObject[] | null> { // 先用同步forEach将所有子节点存入普通数组 const children: DataSnapshot[] = []; snap.forEach(child => { children.push(child); }); // 为每个子节点生成解析Promise const promises = children.map(child => obj_from_snap(child)); // 等待所有Promise完成,拿到最终的CustomObject数组 const result = await Promise.all(promises); // 可选:过滤掉可能的null值(如果obj_from_snap会返回null) return result.filter(item => item !== null) as CustomObject[]; } export async function obj_from_snap(co: DataSnapshot): Promise<CustomObject | null> { // 先完成异步数据获取,再组装对象 const data = await getData(co.key); // 修复原代码中函数名大小写不一致的问题 if (!data) { return null; // 处理数据获取失败的场景 } return { val1: co.val().val1, val2: co.val().val2, Data: data }; } export async function getData(key: string): Promise<string | null> { // 你的异步逻辑实现 }
关键改动说明
- 同步遍历转数组:用同步的
forEach把所有子节点存入普通数组,彻底避开异步回调的兼容性问题 map生成Promise数组:确保每个子节点都对应一个解析Promise,不会遗漏任何节点Promise.all返回解析结果:Promise.all会自动将所有Promise的最终结果组装成数组,这才是你需要的CustomObject数组- 修复函数名大小写:原代码中
GetData和getData大小写不一致,会导致运行时错误
额外提示
- 如果不需要处理
null值,直接返回await Promise.all(promises)即可 - 同步遍历DataSnapshot是安全的,不会被异步逻辑打断
内容的提问来源于stack exchange,提问作者Chris Whipple
相关产品推荐
相关产品推荐

