Firebase添加聊天打字状态检测后原有消息收发功能失效
问题根因
你遇到的所有异常都是三个低级逻辑错误导致的,和Firebase本身稳定性无关:
- 打字状态存储路径错误,直接覆盖了全部历史聊天记录
你原本的聊天消息存储层级是messages/发送方/接收方/时间戳,所有历史消息都存在messages/发送方/接收方这个父节点下的时间戳子节点里。但你新增打字状态逻辑时,直接对这个父节点执行set()操作——Firebase Realtime Database的set()是全量覆盖写入,不是增量更新,你每次触发打字状态更新,就会把这个路径下之前存的所有聊天消息全部删掉,替换成只有tpg字段的对象。后续拉取消息时找不到原本的usr、msg字段,自然会显示undefined,历史消息也会全部丢失。 - 打字状态监听缺少空值判断,直接触发JS报错打断逻辑
页面刚加载时对方还没触发过打字动作,你监听的打字状态节点初始是空值,snapshot.val()会返回null,这时候你直接执行typing.tpg会直接抛出JS类型错误,打断后续所有页面逻辑,这也是消息有时加载不出来的原因之一。 - 用户名非空校验逻辑位置不对,没有拦住空输入
你用prompt()获取昵称和聊天对象时,如果用户点击弹窗的取消按钮,返回值就是null,如果校验逻辑没写在所有数据库操作的最前面提前终止代码运行,就会往数据库里写入messages/null/null这种异常节点,生成你看到的null用户发消息的脏数据。
另外你原代码里"yes"、"no"的闭合引号用了中文全角符号,也会导致JS解析异常。
修复步骤
- 把打字状态数据挪到独立的
typing_status根节点存储,和聊天消息的messages节点完全隔离,从根源上避免覆盖聊天记录 - 在代码最开头添加严格的非空校验,只要用户名、接收方名为空或者用户点了取消,直接终止后续逻辑
- 所有读取数据库快照的位置,先判断快照值是否存在,再读取内部字段,避免空值报错
- 渲染聊天消息时加字段校验,只有存在
usr和msg字段的节点才当成消息渲染,过滤异常脏数据
修复后可直接运行的完整代码
firebase.initializeApp(firebaseConfig); const db = firebase.database(); // 最开头做非空校验,拦住空输入 const username = prompt("Nickname:"); const receiver = prompt("Receiver's name:"); if (!username || !receiver || !username.trim() || !receiver.trim()) { alert("昵称和聊天对象名称不能为空"); throw new Error("用户名或接收方名为空,终止运行"); } // 发送消息逻辑 document.getElementById("send-message").addEventListener("submit", postChat); function postChat(e) { e.preventDefault(); const timestamp = Date.now(); const chatTxt = document.getElementById("chat-txt"); const message = chatTxt.value.trim(); if (!message) return; // 空消息不发送 chatTxt.value = ""; // 双向写入消息记录 db.ref("messages/" + username + "/" + receiver + "/" + timestamp).set({ usr: username, msg: message, }); db.ref("messages/" + receiver + "/" + username + "/" + timestamp).set({ usr: username, msg: message, }); sendTyping(false); } // 拉取并渲染消息 const fetchChat = db.ref("messages/" + username + "/" + receiver + "/"); fetchChat.on("child_added", function (snapshot) { const messages = snapshot.val(); // 加字段校验,只有是正常消息才渲染,过滤异常数据 if (!messages || !messages.usr || !messages.msg) return; const msg = "<li>" + messages.usr + " : " + messages.msg + "</li>"; document.getElementById("messages").innerHTML += msg; }); // 打字状态逻辑,单独存在typing_status节点,不和消息混放 function sendTyping(isTyping) { // 路径改成独立的typing_status节点,不会覆盖消息 db.ref("typing_status/" + username + "/" + receiver).set({ tpg: isTyping ? "yes" : "no" // 全部使用英文半角引号 }); } var searchTimeout; document.getElementById("chat-txt").onkeydown = function() { if (searchTimeout != undefined) clearTimeout(searchTimeout); searchTimeout = setTimeout(() => sendTyping(false), 1500); sendTyping(true); } // 监听对方打字状态 const areTheyTyping = db.ref("typing_status/" + receiver + "/" + username); areTheyTyping.on("value", function(snapshot) { const typingData = snapshot.val(); // 先判空再取字段,避免报错 const status = typingData?.tpg; if (status == "yes") { document.getElementById("writing").innerHTML = "typing..."; } else { document.getElementById("writing").innerHTML = ""; } });
额外注意
你之前已经写入数据库的null节点、被覆盖的坏数据,可以直接去Firebase控制台手动删掉,不然会一直被拉取到前端显示异常。
内容的提问来源于stack exchange,提问作者pmisrfvfpimm23
相关产品推荐
相关产品推荐

