Firebase登录更新用户文档与onAuthStateChange冲突问题
解决Firebase Auth与Firestore写操作的时序冲突问题
问题原因
当signInWithPopup完成登录后,Firebase Auth会立即触发onAuthStateChanged监听器,但此时你调用的setDoc(带merge: true)异步写操作可能还没完成持久化到Firestore。这时候用getDoc读取文档,会拿到写操作执行后的中间状态(即使合并空对象不修改实际数据,Firestore仍会生成更新事件),导致读取结果为空对象。添加延迟能解决是因为给了写操作足够的时间完成,但这是不可靠的临时方案。
解决方案
方案1:使用Firestore实时快照监听(推荐)
用onSnapshot代替getDoc,它会自动监听文档变化,无论写操作何时完成,都能拿到最新的文档数据:
export const initAuth = (): void => { auth.onAuthStateChanged((user) => { if (user) { const docRef = doc(getFirestore(), 'users', user.uid); // 订阅文档快照变化 const unsubscribe = onSnapshot(docRef, (doc) => { if (doc.exists()) { const userData = doc.data(); console.log(userData); // 始终获取最新的完整文档数据 // 若仅需一次结果,获取后可取消订阅 unsubscribe(); } }); } }); };
方案2:在登录流程中等待写操作完成后再读取
修改登录函数,确保setDoc执行完成后再读取文档,避免依赖onAuthStateChanged的自动触发:
export const signInAndUpdateUserObject = (provider: AuthProvider) => async (): Promise<any> => { try { const { user } = (await signInWithPopup(provider)()) ?? {}; if (!user) return null; // 等待合并更新完成 await setDoc(doc(getFirestore(), 'users', user.uid), {}, { merge: true }); // 写操作完成后读取文档 const docRef = doc(getFirestore(), 'users', user.uid); const docSnap = await getDoc(docRef); return docSnap.exists() ? docSnap.data() : null; } catch (err) { console.error(err); return null; } }; // 调用示例 // signInAndUpdateUserObject(googleProvider)().then(userDoc => { // if (userDoc) { // console.log('完整用户文档:', userDoc); // } // });
方案3:兜底重试机制(临时过渡方案)
如果不想大幅改动现有逻辑,可以在读取为空时重试几次,直到获取到完整数据:
export const initAuth = (): void => { auth.onAuthStateChanged(async (user) => { if (user) { const fetchUserDoc = async (retries = 3) => { const docRef = doc(getFirestore(), 'users', user.uid); const docSnap = await getDoc(docRef); if (docSnap.exists() && Object.keys(docSnap.data()).length > 0) { console.log(docSnap.data()); return; } if (retries > 0) { setTimeout(() => fetchUserDoc(retries - 1), 500); } else { console.error('多次尝试后仍无法获取完整用户文档'); } }; fetchUserDoc(); } }); };
内容的提问来源于stack exchange,提问作者sir-haver
相关产品推荐
相关产品推荐

