Cloud Functions 多Ref更新:解决第四Ref先于第五Ref执行的时序问题
时序问题的核心原因
- 循环内部提前触发返回:你在第四个Ref的处理循环内,第一次迭代就执行了
return Promise.all(promises),直接跳出了for循环。此时promises数组仅包含第一个gameId对应的读取任务,完全没有覆盖arr里的所有待处理项,返回给下游的Promise.all自然会提前完成,触发第五个Ref的更新操作。 - 全局外层定义的
arr、promises数组未做请求隔离:云函数实例可能被复用来处理多次请求,外层定义的数组如果不清空,会残留上一次请求的遍历结果和Promise引用,引发不可预期的执行顺序错乱。 - 循环内使用
var声明变量:存在闭包变量捕获的潜在问题,旧引擎环境下所有异步回调可能拿到循环最后一次的gameId值,导致读取错误的Ref路径。
代码修正方案
修正后的实现去掉了循环内的提前返回,把Promise数组的作用域收窄到当前请求的处理流程内,确保所有第四个Ref的读取、值累加操作全部完成后,再进入第五个Ref的更新步骤:
exports.updateMultipleRefs = functions.https.onCall((data, context) => { // ... values from data used in refs const fetchFromFirstRef = admin.database().... const updateAtSecondRef = admin.database().... const runTransactionAtSecondRef = admin.database().... const fetchArrDataFromThirdRef = admin.database().... const loopArrAtFourthRef = admin.database().... const updateFifthRef = admin.database().... // 把临时变量的作用域收窄到请求处理流程内,避免跨请求残留 let val_1 = 0; let val_2 = 0; return fetchFromFirstRef .once('value') .then((snapshot) => { if (!snapshot.exists()) { return null; } snapshot.forEach((childSnapshot) => { // val_1 += data from each childSnapshot }); return updateAtSecondRef.update({ "val_1":val_1 }) .then(()=> { return runTransactionAtSecondRef.set(admin.database.ServerValue.increment(1)); }) .then(() => { return fetchArrDataFromThirdRef.once('value'); }) .then((thirdSnapshot) => { // 把数组的声明放到当前处理块内,请求间完全隔离 const arr = []; if (thirdSnapshot.exists()) { thirdSnapshot.forEach((childSnapshot) => { const gameId = childSnapshot.key; arr.push(gameId); }); } // 构建当前请求专属的Promise数组 const fourthRefTasks = []; for (let i = 0; i < arr.length; i++) { const gameId = arr[i]; const targetRef = loopArrAtFourthRef.child(gameId); // 把每个Ref的读取任务推入数组,循环全程不做提前返回 fourthRefTasks.push( targetRef.once('value').then((snap) => { snap.forEach((child) => { // val_2 += data from each child }); }) ); } // 遍历完成后,返回覆盖所有任务的Promise.all,等所有任务完成再进入下游流程 return Promise.all(fourthRefTasks); }) .then(() => { // 走到这里时,所有第四个Ref的读取、val_2累加已经全部完成 return updateFifthRef.update({ "val_2": val_2 }); }); }) .catch((error) => { console.log('error', error); return null; }); });
可选优化(适配Swift开发习惯)
可以把回调函数改成async语法,用await替代嵌套的.then链,逻辑结构和Swift原生的异步流程基本一致,更容易理清执行顺序:
exports.updateMultipleRefs = functions.https.onCall(async (data, context) => { try { // 第一阶段:拉取第一个Ref数据 const firstSnapshot = await fetchFromFirstRef.once('value'); if (!firstSnapshot.exists()) return null; let val_1 = 0; firstSnapshot.forEach(childSnapshot => { // val_1 += childSnapshot 中的对应值 }); // 第二阶段:更新第二个Ref await updateAtSecondRef.update({ "val_1": val_1 }); await runTransactionAtSecondRef.set(admin.database.ServerValue.increment(1)); // 第三阶段:拉取第三个Ref的数组数据 const thirdSnapshot = await fetchArrDataFromThirdRef.once('value'); const arr = []; if (thirdSnapshot.exists()) { thirdSnapshot.forEach(childSnapshot => arr.push(childSnapshot.key)); } // 第四阶段:循环拉取第四个Ref的所有对应数据,并行执行所有读取任务 let val_2 = 0; const fourthRefTasks = arr.map(gameId => { return loopArrAtFourthRef.child(gameId).once('value').then(snap => { snap.forEach(child => { // val_2 += child 中的对应值 }); }); }); // 等所有读取任务完成 await Promise.all(fourthRefTasks); // 第五阶段:更新第五个Ref,此时val_2已经是所有第四个Ref数据累加完成的值 await updateFifthRef.update({ "val_2": val_2 }); return null; } catch (error) { console.log('error', error); return null; } });
内容的提问来源于stack exchange,提问作者Lance Samaria
相关产品推荐
相关产品推荐

