AngularFire7 runTransaction报Maximum call stack exceeded错误
此前曾发布相关问题帖但该帖已被关闭,检索到其他用户反馈过高度相似的报错,但始终未找到网络上的有效解答。当前使用Firebase Realtime Database,数据库中对应节点结构如下:
private: { $user_uid: { formula: { formula_quota: 30, quota_used: 0 } } }
业务逻辑为按钮触发数据发布操作,发布流程完成后执行事务逻辑读取配额对象并完成更新,以下为配额事务的核心实现代码(已剔除未使用、已注释的无关代码):
quotaTransaction ( userUID:string, projectUID:string, state:EditorState, asObject:boolean, asProject:boolean ) { const quotaPath = `/private/${ userUID }/formula`; const quotaDoc = ref( this.db, quotaPath ); runTransaction( quotaDoc, quota => { let nbPublish = 0; if ( asObject ) nbPublish++; if ( asProject ) nbPublish++; let quotaUsed = quota.quota_used; let formulaQuota = quota.formula_quota; if ( nbPublish + quotaUsed > formulaQuota ) return ( undefined ); return ( { quota_used: quotaUsed + nbPublish, formula_quota:formulaQuota } ); } ).catch( console.log ); }
- 首次执行
quotaTransaction方法时,抛出栈溢出错误:
RangeError: Maximum call stack size exceeded
at ChildrenNode.isLeafNode (vendor.js:52413:13)
at ChildrenNode.equals (vendor.js:20990:22)
at ChildrenNode.equals (vendor.js:20995:31)
at ChildrenNode.equals (vendor.js:20995:31)
at ChildrenNode.equals (vendor.js:20995:31)
at ChildrenNode.equals (vendor.js:20995:31)
at ChildrenNode.equals (vendor.js:20995:31)
at ChildrenNode.equals (vendor.js:20995:31)
at ChildrenNode.equals (vendor.js:20995:31)
at ChildrenNode.equals (vendor.js:20995:31)
- 第二次执行该方法时,抛出路径类型错误:
TypeError: childPathObj.split is not a function
at pathChild (vendor.js:17766:38)
at ImmutableTree.findRootMostMatchingPathAndValue (vendor.js:23435:30)
at ImmutableTree.findRootMostMatchingPathAndValue (vendor.js:23432:51)
at ImmutableTree.findRootMostMatchingPathAndValue (vendor.js:23432:51)
at ImmutableTree.findRootMostValueAndPath (vendor.js:23456:17)
at compoundWriteGetCompleteNode (vendor.js:55532:45)
at compoundWriteChildCompoundWrite (vendor.js:55574:27)
at writeTreeCalcCompleteEventCache (vendor.js:55887:19)
at syncTreeCalcCompleteEventCache (vendor.js:57565:10)
at repoGetLatestState (vendor.js:59219:10)
两个报错均由事务回调未覆盖边界场景导致:
Firebase Realtime Database事务首次执行回调时,如果本地没有对应节点的缓存,传入的当前值参数quota会是null。原代码未做判空直接访问quota.quota_used、quota.formula_quota,拿到的是undefined,后续计算得到的更新对象包含NaN、undefined等非法值,触发SDK内部节点递归比较逻辑死循环,直接导致栈溢出。第一次执行失败后本地缓存了异常状态值,第二次执行时就会触发路径解析的类型错误。
额外问题:原代码在配额不足时返回undefined,会被SDK判定为删除当前节点,不符合业务逻辑预期。
调整事务回调逻辑,覆盖空值场景、修正返回值,修复后代码如下:
runTransaction( quotaDoc, quota => { // 处理本地无缓存、节点不存在的场景 if (quota === null) { // 按业务规则初始化配额值 return { formula_quota: 30, quota_used: 0 } } let nbPublish = 0; if (asObject) nbPublish++; if (asProject) nbPublish++; // 增加类型兜底,兼容历史脏数据 const quotaUsed = Number(quota.quota_used) || 0; const formulaQuota = Number(quota.formula_quota) || 0; // 配额不足时直接返回当前节点值,终止本次更新,不要返回undefined if (nbPublish + quotaUsed > formulaQuota) { return quota; } return { quota_used: quotaUsed + nbPublish, formula_quota: formulaQuota }; } )
核心修复点:
- 事务回调开头必须判断入参是否为
null,覆盖本地无缓存、节点首次创建的场景 - 配额校验不通过时返回当前读取到的节点值,不要返回
undefined,避免误删节点 - 对读取到的配额字段增加类型转换兜底,避免历史脏数据(字段缺失、值类型错误)引发计算异常
内容的提问来源于stack exchange,提问作者JSmith

