首次注册调用getUserVotes报错threadSnap.data不是函数求助
React + Firebase 首次注册调用getUserVotes时出现threadSnap.data不是函数的错误
问题背景
技术栈:React, Firebase, Next.js
- 用户注册时,系统会在
User集合创建用户文档,并在其下生成包含threads、replies两个文档的vote子集合。 - 注册完成后,通过
useEffect调用getUserInfo()和getUserVotes读取已创建的文档。 - 问题:首次注册调用
getUserVotes时,抛出threadSnap.data不是函数的错误(预期threadSnap是Firestore文档快照),但刷新页面后错误消失。
相关代码:
const getUserVotes = async (user) => { const uid = user.uid; if (user) { Promise.allSettled([ getDoc(doc(db, "users", uid, "votes", "threads")), getDoc(doc(db, "users", uid, "votes", "replies")) ]).then(([threadSnap, replySnap]) => { setUserVotes({ threads: threadSnap.data(), replies: replySnap.data() }) }) } }
尝试过程:
- 最初用两次
await调用getDoc,误以为JS在Promise完成前就执行了setUserVotes; - 改用
Promise.all后状态变为undefined; - 最终使用
Promise.allSettled,仅首次注册时出现上述错误。 - 期望:等待两个
getDoc解析完成后,再通过setUserVotes设置状态。
问题分析
Promise.allSettled结果处理错误:Promise.allSettled返回的数组元素不是直接的文档快照,而是包含status和value(成功时)或reason(失败时)的结果对象。你直接调用threadSnap.data(),相当于在这个结果对象上调用不存在的data方法,这是报错的核心原因。注册与读取的时序问题:
首次注册时,Firestore的写操作(创建votes子集合下的文档)可能还未完全同步到服务器,导致读取时文档快照的exists()为false。不过这不是data不是函数的直接原因,但会导致后续数据异常。
解决方案
方案1:正确处理Promise.allSettled的返回结果
修改getUserVotes代码,先提取成功的文档快照,再调用data()方法:
const getUserVotes = async (user) => { const uid = user.uid; if (!user) return; const results = await Promise.allSettled([ getDoc(doc(db, "users", uid, "votes", "threads")), getDoc(doc(db, "users", uid, "votes", "replies")) ]); // 提取成功的文档快照,失败时设为null const threadSnap = results[0].status === 'fulfilled' ? results[0].value : null; const replySnap = results[1].status === 'fulfilled' ? results[1].value : null; // 安全读取数据,文档不存在时用默认空对象 setUserVotes({ threads: threadSnap?.exists() ? threadSnap.data() : {}, replies: replySnap?.exists() ? replySnap.data() : {} }); }
方案2:确保注册写操作完成后再触发读取
调整注册逻辑,等待所有写操作(用户文档、votes子集合文档)完成后,再设置用户状态触发useEffect的读取:
const handleRegister = async (userData) => { try { // 创建用户认证 const userCredential = await createUserWithEmailAndPassword(auth, userData.email, userData.password); const user = userCredential.user; // 等待所有Firestore写操作完成 await Promise.all([ // 创建User集合主文档 setDoc(doc(db, "users", user.uid), { ...userData, uid: user.uid }), // 创建votes子集合的threads文档 setDoc(doc(db, "users", user.uid, "votes", "threads"), {}), // 创建votes子集合的replies文档 setDoc(doc(db, "users", user.uid, "votes", "replies"), {}) ]); // 设置用户状态,触发useEffect中的读取逻辑 setCurrentUser(user); } catch (error) { console.error("注册失败:", error); } }
方案3:改用Promise.all配合await(更简洁)
如果注册时已经确保所有文档会被创建,无需处理失败场景,直接用Promise.all+await等待所有读取完成:
const getUserVotes = async (user) => { const uid = user.uid; if (!user) return; try { const [threadSnap, replySnap] = await Promise.all([ getDoc(doc(db, "users", uid, "votes", "threads")), getDoc(doc(db, "users", uid, "votes", "replies")) ]); setUserVotes({ threads: threadSnap.exists() ? threadSnap.data() : {}, replies: replySnap.exists() ? replySnap.data() : {} }); } catch (error) { console.error("获取投票数据失败:", error); } }
内容的提问来源于stack exchange,提问作者philip7865
相关产品推荐
相关产品推荐

