You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

首次注册调用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设置状态。

问题分析

  1. Promise.allSettled结果处理错误:
    Promise.allSettled返回的数组元素不是直接的文档快照,而是包含status和value(成功时)或reason(失败时)的结果对象。你直接调用threadSnap.data(),相当于在这个结果对象上调用不存在的data方法,这是报错的核心原因。

  2. 注册与读取的时序问题:
    首次注册时,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 06:30:59