JavaScript中回调与Promise失效:Firebase异步数据加载异常
解决Firebase异步加载导致
populateUsers()提前执行的问题 我明白你遇到的异步加载坑了——Firebase的事件回调都是异步触发的,嵌套的child_added和value事件很容易让代码执行顺序乱掉,之前的Promise和回调没生效,大概率是因为没把所有异步操作统一管理起来。咱们一步步来修复:
先理清核心问题
- 你现在的写法是点击按钮后,先执行
set(true),然后立刻监听child_added——但child_added会触发所有已存在的子节点+新增的子节点,每次触发又会发起一个异步的value请求,这些请求的完成时间不确定,导致populateUsers可能在数据还没返回时就被调用(或者你希望一次性加载完所有用户再渲染)。 - 你的
populateUsers函数参数是单个用户对象,但代码里用了chattedWith.length,这明显是逻辑矛盾,得先修正这个。
方案1:只处理刚新增的聊天对象(最贴合你点击按钮的场景)
如果你只是想在点击添加按钮后,把刚新增的那个聊天用户渲染出来,完全不需要监听child_added,直接在set完成后获取对应用户数据就行:
$("body").on("click", ".si-add", function(e) { var _CU = $.cookie("_cu"), _MT = $(this).parent().attr("data-id"); // 先完成聊天关系的设置,等待操作完成 _firebaseChatRef.child(_CU).child(_MT).set(true) .then(function() { // 设置成功后,一次性获取该用户的auth数据(用once代替on,避免实时监听) return _firebaseAuthRef.child(_MT).once("value"); }) .then(function(userSnapshot) { // 数据返回后再调用渲染函数 populateUsers({ name: userSnapshot.val().n, id: userSnapshot.key }); }) .catch(function(error) { console.error("加载用户数据失败:", error); }); function populateUsers(chattedWith) { let _el = document.getElementsByClassName("chat-ind-body-wrap")[0]; // 修正原代码中错误的length引用,直接渲染单个用户 _el.insertAdjacentHTML('afterbegin', ` <li class="chat-ind-user active-user" data-id="${chattedWith.id}"> <div class="chat-ind-user-img"> <img src="assets/1.jpg"> </div> <div class="chat-ind-user-data"> <div class="ciu-name"> ${chattedWith.name} </div> <div class="ciu-text"> Hi, I am the text here ! </div> <span class="ciu-time"> 2h ago </span> </div> <span class="user-availble"> <i class="fa fa-circle"></i> </span> </li> `); } });
方案2:加载所有已存在的聊天对象
如果你需要把当前用户所有的聊天对象都渲染出来,就得把所有用户数据的异步请求包装成Promise,用Promise.all等待全部加载完成后再渲染:
$("body").on("click", ".si-add", function(e) { var _CU = $.cookie("_cu"), _MT = $(this).parent().attr("data-id"); // 先完成新增聊天节点的操作 _firebaseChatRef.child(_CU).child(_MT).set(true) .then(function() { // 获取当前用户所有的聊天关系节点 return _firebaseChatRef.child(_CU).once("value"); }) .then(function(chatSnapshot) { // 遍历所有聊天节点,生成获取用户数据的Promise数组 const userPromises = []; chatSnapshot.forEach(function(childSnapshot) { const userId = childSnapshot.key; userPromises.push(_firebaseAuthRef.child(userId).once("value")); }); // 等待所有用户数据加载完成 return Promise.all(userPromises); }) .then(function(userSnapshots) { // 把所有用户快照转换成需要的对象格式 const users = userSnapshots.map(function(snap) { return {name: snap.val().n, id: snap.key}; }); // 调用渲染函数,此时所有数据都已加载完成 populateUsers(users); }) .catch(function(error) { console.error("加载数据失败:", error); }); function populateUsers(chattedWithList) { let _el = document.getElementsByClassName("chat-ind-body-wrap")[0]; // 遍历用户数组,逐个渲染DOM元素 chattedWithList.forEach(function(chattedWith) { _el.insertAdjacentHTML('afterbegin', ` <li class="chat-ind-user active-user" data-id="${chattedWith.id}"> <div class="chat-ind-user-img"> <img src="assets/1.jpg"> </div> <div class="chat-ind-user-data"> <div class="ciu-name"> ${chattedWith.name} </div> <div class="ciu-text"> Hi, I am the text here ! </div> <span class="ciu-time"> 2h ago </span> </div> <span class="user-availble"> <i class="fa fa-circle"></i> </span> </li> `); }); } });
为什么之前的方法没生效?
- 你之前的
set(true).then()里是空的,没有把后续的用户数据请求纳入Promise链,导致异步操作还是各自为政。 - 用
on("child_added")会实时监听节点变化,包括之前已存在的子节点,每次触发都会发起新的异步请求,你没有等待这些请求全部完成就可能执行了后续逻辑。 - 尽量用
once("value")代替on("value"),除非你需要实时监听数据变化——一次性获取数据更高效,还能避免不必要的内存泄漏。
内容的提问来源于stack exchange,提问作者Shehny Khan
相关产品推荐
相关产品推荐

