Latency补偿与Firestore事务问题:onSnapshot未更新本地变量
我完全懂你遇到的麻烦——用事务更新文档后,本地的mylocalvariable没有立刻同步最新数据,导致selectnextrecord()总是拿到旧数据,重复选中同一个文档。这核心原因是Firestore的onSnapshot回调是异步触发的:事务提交成功后,Firestore需要一点时间把更新同步到客户端的快照监听,这时候你立刻调用selectnextrecord(),自然还是用的更新前的旧数据。
下面给你两种实用的解决方案,你可以根据业务场景选择:
方案1:让快照更新自动触发下一个文档的选择
这种方式最省心,直接把获取下一个文档的逻辑绑定到onSnapshot的回调里,每次数据变化(包括事务更新后)都会自动刷新最新的未处理文档:
// 初始化本地变量 let mylocalvariable = []; let nextrecord = null; // 监听集合快照,数据变化时自动更新本地数据并获取下一个文档 db.collection('/mycollection').onSnapshot(querySnapshot => { mylocalvariable = querySnapshot.docs; // 每次快照更新时自动查找最新的未处理文档 selectnextrecord(); }); function selectnextrecord(){ // 从最新的本地数据中筛选首个未处理的文档 nextrecord = mylocalvariable.find(x => !x.data().done); console.log(nextrecord); } function submitchanges(){ if (!nextrecord) return; const sfDocRef = db.collection('/mycollection').doc(nextrecord.id); return db.runTransaction(transaction => { return transaction.get(sfDocRef).then(sfDoc => { if (!sfDoc.exists) { throw "Document does not exist!"; } transaction.update(sfDocRef, { done: true }); }); }).then(() => { console.log("Transaction succeeded!"); // 这里不用手动调用selectnextrecord,onSnapshot会自动处理 }).catch(error => { console.log("Transaction failed: ", error); }); }
方案2:按需触发文档刷新(适合不需要每次快照都更新的场景)
如果不想每次数据变化都自动获取下一个文档,只想在事务提交后触发刷新,可以加一个状态变量来控制:
let mylocalvariable = []; let nextrecord = null; // 标记是否需要刷新下一个文档 let needsRefreshNext = false; db.collection('/mycollection').onSnapshot(querySnapshot => { mylocalvariable = querySnapshot.docs; // 检查是否需要刷新,是则执行并重置标记 if (needsRefreshNext) { selectnextrecord(); needsRefreshNext = false; } }); function selectnextrecord(){ nextrecord = mylocalvariable.find(x => !x.data().done); console.log(nextrecord); } function submitchanges(){ if (!nextrecord) return; const sfDocRef = db.collection('/mycollection').doc(nextrecord.id); return db.runTransaction(transaction => { return transaction.get(sfDocRef).then(sfDoc => { if (!sfDoc.exists) { throw "Document does not exist!"; } transaction.update(sfDocRef, { done: true }); }); }).then(() => { console.log("Transaction succeeded!"); // 标记需要刷新,等待onSnapshot触发后处理 needsRefreshNext = true; }).catch(error => { console.log("Transaction failed: ", error); }); }
为什么原代码会出问题?
简单来说,事务提交和onSnapshot的更新不同步:你调用transaction.update()后,Firestore需要先完成服务器端的事务处理,再把更新同步到客户端的快照监听,这个过程是异步的。原代码里在事务的then里立刻调用selectnextrecord(),此时mylocalvariable还没收到最新的快照数据,所以还是会找到那个刚被标记为done但本地还没更新的文档。
额外提示
不建议手动修改mylocalvariable里的文档数据来“即时更新”——因为onSnapshot下一次触发时会完全覆盖mylocalvariable,反而可能导致数据不一致。依赖onSnapshot的自动同步是最安全的方式。
内容的提问来源于stack exchange,提问作者Luke Armstrong

