Firebase创建用户后数据更新被覆盖,如何监听用户写入数据库?
首先得纠正一个常见的误解:createUserWithEmailAndPassword是Firebase Auth的专属API,它只会在Auth系统里创建用户身份记录,不会自动帮你把用户数据写到实时数据库里。你数据库里的用户数据,要么是你自己在客户端写代码存的,要么是配置了云函数的Auth onCreate触发器自动同步的。针对这两种情况,监听数据写入完成的方式不一样:
如果你是客户端手动写数据库
这种情况最简单,直接用async/await或者Promise链式调用,等数据库写入的Promise完成就好了,或者也可以监听目标节点的value事件来确认:
// 用async/await等待整个流程完成 const res = await secondaryApp.auth().createUserWithEmailAndPassword(user.email, password); // 等数据库写入完成再继续 await firebase.database().ref(`users/${res.user.uid}`).set({ public: { ...user } }); console.log("用户数据已经成功写到数据库啦"); // 或者用一次性监听来确认数据存在 const userRef = firebase.database().ref(`users/${res.user.uid}`); userRef.once('value', (snapshot) => { if (snapshot.exists()) { console.log("数据库里已经有这个用户的数据了"); // 这里可以接着干你要做的事 } });
如果是云函数自动同步用户数据到数据库
这种情况下,客户端没法直接控制云函数的执行时机,所以可以监听数据库里的用户节点,直到数据出现:
const res = await secondaryApp.auth().createUserWithEmailAndPassword(user.email, password); const userRef = firebase.database().ref(`users/${res.user.uid}`); // 监听节点变化,直到云函数把数据写进来 const unsubscribe = userRef.on('value', (snapshot) => { if (snapshot.exists()) { console.log("云函数已经把用户数据同步到数据库了"); // 执行后续操作 unsubscribe(); // 记得取消监听,不然会一直触发 } });
先回到那个关键误解:createUserWithEmailAndPassword不会自动写数据库,你遇到的“Auth用户创建好了,但数据库里还没数据”,肯定是因为数据库的写入操作是异步的(比如云函数在后台跑,或者客户端写入有延迟),导致你的更新操作抢先执行,最后被后续的初始数据写入给覆盖了。
结合你贴的代码来看,你是自己在客户端创建Auth用户后调用set()写数据库,但可能存在两种情况:要么你同时还有云函数在写同一个节点,要么是代码逻辑里的顺序问题?下面给你几个可行的解决方案:
方案1:用update()代替set()来更新数据
set()会把整个节点的内容全部替换掉,而update()只会更新你指定的字段。如果你只是要修改部分字段(比如头像),换成update()就能避免覆盖:
// 只更新avatar和thumbnail这两个字段 await firebase.database().ref(`users/${res.user.uid}`).update({ "public.avatar": imageUrl, "public.thumbnail": imageUrl });
如果是云函数在写初始数据,你还可以给云函数加个判断:只有当数据库里没有这个用户节点时才写入初始数据,这样就不会和客户端的更新冲突了:
// 云函数里的代码示例 exports.createUserRecord = functions.auth.user().onCreate(async (user) => { const userRef = admin.database().ref(`users/${user.uid}`); const snapshot = await userRef.once('value'); if (!snapshot.exists()) { // 只有节点不存在的时候才写初始数据 await userRef.set({ public: { email: user.email, // 其他你要设置的初始字段 } }); } });
方案2:把初始数据和更新数据一次性写入
看你的代码,你是先创建Auth用户,然后处理头像,再写数据库。其实可以把所有要设置的字段整合到一起,一次性用set()写入,这样就不存在“先写初始数据再更新”的步骤,自然不会有覆盖问题:
const res = await secondaryApp.auth().createUserWithEmailAndPassword(user.email, password); await secondaryApp.auth().signOut(); // 先把基础用户数据准备好 const userData = { public: { ...user } }; if (newAvatar) { const type = newAvatar.type.slice(6); const { blob } = newAvatar; const ref = storage.child(`users/${res.user.uid}.${type}`); const image = await ref.put(blob); const imageUrl = await image.ref.getDownloadURL(); // 直接把头像信息加到要写入的数据里 userData.public.avatar = imageUrl; userData.public.thumbnail = imageUrl; } // 一次性写入所有数据 await firebase.database().ref(`users/${res.user.uid}`).set(userData); // 再发密码重置邮件 await firebase.auth().sendPasswordResetEmail(user.email); done("success", "Successfull user creation.", `A new user has been created.`);
方案3:等数据库初始数据写入完成再更新
如果确实需要分开执行(比如初始数据必须由云函数写),那可以监听数据库节点,等初始数据出现后再执行更新:
const res = await secondaryApp.auth().createUserWithEmailAndPassword(user.email, password); await secondaryApp.auth().signOut(); const userRef = firebase.database().ref(`users/${res.user.uid}`); // 用Promise包装监听,等待初始数据写入 await new Promise((resolve) => { const unsubscribe = userRef.on('value', (snapshot) => { if (snapshot.exists()) { unsubscribe(); resolve(); } }); }); // 现在再处理头像和更新 if (newAvatar) { const type = newAvatar.type.slice(6); const { blob } = newAvatar; const ref = storage.child(`users/${res.user.uid}.${type}`); const image = await ref.put(blob); const imageUrl = await image.ref.getDownloadURL(); await userRef.update({ "public.avatar": imageUrl, "public.thumbnail": imageUrl }); } await firebase.auth().sendPasswordResetEmail(user.email); done("success", "Successfull user creation.", `A new user has been created.`);
内容的提问来源于stack exchange,提问作者Karim Nashaat

