Dialogflow结合Firebase Realtime Database用transaction时响应重复异常排查
重复响应与null初始值的核心成因
- 这个异常和你的参数捕获逻辑没关系,本质是Firebase Realtime Database的
transaction执行机制和Dialogflow的响应写入逻辑错位导致的。 transaction的回调函数从来不是只执行一次:- 第一次发起更新请求时,如果本地没有缓存目标数据库节点的快照值,回调拿到的初始值就是
null,对应输出里第一组的“previous entry was null”。 - 系统会把第一次回调返回的值提交到服务端做一致性校验,如果服务端发现当前节点的实际存储值和本地拿到的初始值不匹配(比如之前已经存了'friends'),就会重新执行一次回调,把服务端真实的当前值传回,重新计算要写入的新值,对应输出里第二组的“previous entry was friends”。
- 第一次发起更新请求时,如果本地没有缓存目标数据库节点的快照值,回调拿到的初始值就是
- 你把
agent.add()的响应写入逻辑直接放在了可能多次执行的transaction回调里,每执行一次回调,就会往Dialogflow的返回队列里追加两段文本,最终就出现了两组重复输出。 - 你观察到数据库更新结果正确,是因为最后一次回调返回的写入值最终和服务端完成了一致性对齐,哪怕中间多次执行回调,最终落库的内容是符合预期的,但响应逻辑因为跟着回调重复执行,就出现了异常的重复输出。
修复方法
- 把会生成Dialogflow返回响应的
agent.add()逻辑,从transaction的回调内部移出去,避免响应写入跟着回调重复执行。 - 用函数作用域的外部变量暂存transaction最后一次执行时拿到的旧节点值,等整个transaction流程完全跑完(所有重试逻辑执行完毕、服务端确认写入完成)之后,再一次性写入响应。
- 注意你当前示例代码的逻辑漏洞:回调函数最后
return directedTo返回的是未修改的旧值,按这段代码运行的话数据库不会完成更新,应该是你写示例时的笔误,实际逻辑里需要返回要写入的新值(也就是angryTo)才能完成数据库更新。
修复后的参考实现
async function angerEmotionCapture(agent) { const angryTo = agent.parameters.angryDirectedTo; let previousEntry = null; // 等待transaction全流程执行完成,避免回调多次执行影响响应 await admin.database().ref('directedTo').transaction((directedTo)=>{ // 回调内仅暂存最终拿到的旧值,不直接写入Dialogflow响应 previousEntry = directedTo; // 返回实际要写入数据库的新值,完成更新 return angryTo; }); // 流程完全结束后统一写入响应,保证仅执行一次 agent.add(`previous entry was ${previousEntry}`); agent.add(`new entry is ${angryTo}`); }
内容的提问来源于stack exchange,提问作者Agustinus Benyamin Prasetyo
相关产品推荐
相关产品推荐

