Javascript Firebase Firestore-异步/等待在继续之前不等待获取数据?

Javascript Firebase Firestore-异步/等待在继续之前不等待获取数据?,javascript,firebase,asynchronous,google-cloud-firestore,async-await,Javascript,Firebase,Asynchronous,Google Cloud Firestore,Async Await,我对JS的“async/await”方面还不熟悉,我正在尝试了解它是如何工作的 我得到的错误是下面代码的第10行。我已经创建了一个firestore数据库,正在尝试从“房间”集合中侦听并获取某个文档。我试图从doc'joiner'获取数据,并使用该数据更新其他元素的innerHTML // References and Variables const db = firebase.firestore(); const roomRef = await db.collection('roo

我对JS的“async/await”方面还不熟悉,我正在尝试了解它是如何工作的

我得到的错误是下面代码的第10行。我已经创建了一个firestore数据库,正在尝试从“房间”集合中侦听并获取某个文档。我试图从doc'joiner'获取数据,并使用该数据更新其他元素的innerHTML

  // References and Variables
  const db = firebase.firestore();
  const roomRef = await db.collection('rooms');
  const remoteNameDOM = document.getElementById('remoteName');
  const chatNameDOM = document.getElementById('title');
  let remoteUser;
  // Snapshot Listener
  roomRef.onSnapshot(snapshot => {
    snapshot.docChanges().forEach(async change => {
      if (roomId != null){
        if (role == "creator"){
          const usersInfo = await roomRef.doc(roomId).collection('userInfo');
          usersInfo.doc('joiner').get().then(async (doc) => {
            remoteUser = await doc.data().joinerName;
            remoteNameDOM.innerHTML = `${remoteUser} (Other)`;
            chatNameDOM.innerHTML = `Chatting with ${remoteUser}`;
          })
        }
      }
    })
  })
})
但是,我得到了一个错误:

Uncaught(in promise)TypeError:无法读取未定义的属性“joinerName”

同样,如果我将第10-12行更改为:

remoteUser = await doc.data();
remoteNameDOM.innerHTML = `${remoteUser.joinerName} (Other)`;
chatNameDOM.innerHTML = `Chatting with ${remoteUser.joinerName}`;
我也犯了同样的错误

我目前的理解是,wait将在继续之前等待行/函数完成,因此remoteUser在尝试调用它之前不应为null。我将提到,有时代码工作正常,DOM元素被更新,并且没有控制台错误

我的问题:我是否错误地考虑了异步/等待调用?这不是我从Firestore获取文件的方式吗?最重要的是,为什么它似乎只在某些时候起作用

编辑:以下是@Dharmaraj要求的Firestore数据库截图。我很感激你的建议。


这方面几乎没有错误:

  • db.collection()不返回承诺,因此不需要等待
  • forEach忽略了承诺,所以您不能在forEach内部实际使用wait。在这种情况下,最好使用of
  • 请尝试以下代码:

    const db = firebase.firestore();
    const roomRef = db.collection('rooms');
    const remoteNameDOM = document.getElementById('remoteName');
    const chatNameDOM = document.getElementById('title');
    let remoteUser;
    // Snapshot Listener
    roomRef.onSnapshot(async (snapshot) => {
      for (const change of snapshot.docChanges()) {
        if (roomId != null){
          if (role == "creator"){
            const usersInfo = roomRef.doc(roomId).collection('userInfo').doc("joiner");
            usersInfo.doc('joiner').get().then(async (doc) => {
              remoteUser = doc.data().joinerName;
              remoteNameDOM.innerHTML = `${remoteUser} (Other)`;
              chatNameDOM.innerHTML = `Chatting with ${remoteUser}`;
            })
          }
        }
      }
    })
    

    这方面几乎没有错误:

  • db.collection()不返回承诺,因此不需要等待
  • forEach忽略了承诺,所以您不能在forEach内部实际使用wait。在这种情况下,最好使用of
  • 请尝试以下代码:

    const db = firebase.firestore();
    const roomRef = db.collection('rooms');
    const remoteNameDOM = document.getElementById('remoteName');
    const chatNameDOM = document.getElementById('title');
    let remoteUser;
    // Snapshot Listener
    roomRef.onSnapshot(async (snapshot) => {
      for (const change of snapshot.docChanges()) {
        if (roomId != null){
          if (role == "creator"){
            const usersInfo = roomRef.doc(roomId).collection('userInfo').doc("joiner");
            usersInfo.doc('joiner').get().then(async (doc) => {
              remoteUser = doc.data().joinerName;
              remoteNameDOM.innerHTML = `${remoteUser} (Other)`;
              chatNameDOM.innerHTML = `Chatting with ${remoteUser}`;
            })
          }
        }
      }
    })
    

    您混合使用了
    async/await
    then()
    ,这是不推荐的。我在下面提出了一个基于
    Promise.all()
    的解决方案,它有助于理解代码中涉及的不同数组。您可以使用@Dharmaraj建议的
    async/await
    for of
    循环对其进行调整

    roomRef.onSnapshot((snapshot) => {
        // snapshot.docChanges() Returns an array of the documents changes since the last snapshot.
        // you may check the type of the change. I guess you maybe don’t want to treat deletions
    
        const promises = [];
        snapshot.docChanges().forEach(docChange => {
            // No need to use a roomId, you get the doc via docChange.doc
            // see https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentChange
    
            if (role == "creator") {  // It is not clear from where you get the value of role...
                const joinerRef = docChange.doc.collection('userInfo').doc('joiner');
                promises.push(joinerRef.get());
            }
    
        });
    
        Promise.all(promises)
            .then(docSnapshotArray => {
                // docSnapshotArray is an Array of all the docSnapshots
                // corresponding to all the joiner docs corresponding to all 
                // the rooms that changed when the listener was triggered
                docSnapshotArray.forEach(docSnapshot => {
                    remoteUser = docSnapshot.data().joinerName;
                    remoteNameDOM.innerHTML = `${remoteUser} (Other)`;
                    chatNameDOM.innerHTML = `Chatting with ${remoteUser}`;
                })
            });
    
    });
    
    但是,我不清楚的是如何区分“第一个”
    快照的不同元素(即
    roomRef.onSnapshot((快照)=>{…}))
    )。如果多个
    文件室
    发生更改,则
    snapshot.docChanges()
    数组将包含多个更改,最后,您将覆盖上一个循环中的
    remoteNameDOM
    chatNameDOM
    元素

    或者您预先知道此“first”
    快照将始终包含一个文档(因为您的应用程序的体系结构),然后您可以通过如下方式处理第一个和唯一的元素来简化代码:

    roomRef.onSnapshot((snapshot) => {
        const roomDoc = snapshot.docChanges()[0];
        // ...
    
    });
    

    您混合使用了
    async/await
    then()
    ,这是不推荐的。我在下面提出了一个基于
    Promise.all()
    的解决方案,它有助于理解代码中涉及的不同数组。您可以使用@Dharmaraj建议的
    async/await
    for of
    循环对其进行调整

    roomRef.onSnapshot((snapshot) => {
        // snapshot.docChanges() Returns an array of the documents changes since the last snapshot.
        // you may check the type of the change. I guess you maybe don’t want to treat deletions
    
        const promises = [];
        snapshot.docChanges().forEach(docChange => {
            // No need to use a roomId, you get the doc via docChange.doc
            // see https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentChange
    
            if (role == "creator") {  // It is not clear from where you get the value of role...
                const joinerRef = docChange.doc.collection('userInfo').doc('joiner');
                promises.push(joinerRef.get());
            }
    
        });
    
        Promise.all(promises)
            .then(docSnapshotArray => {
                // docSnapshotArray is an Array of all the docSnapshots
                // corresponding to all the joiner docs corresponding to all 
                // the rooms that changed when the listener was triggered
                docSnapshotArray.forEach(docSnapshot => {
                    remoteUser = docSnapshot.data().joinerName;
                    remoteNameDOM.innerHTML = `${remoteUser} (Other)`;
                    chatNameDOM.innerHTML = `Chatting with ${remoteUser}`;
                })
            });
    
    });
    
    但是,我不清楚的是如何区分“第一个”
    快照的不同元素(即
    roomRef.onSnapshot((快照)=>{…}))
    )。如果多个
    文件室
    发生更改,则
    snapshot.docChanges()
    数组将包含多个更改,最后,您将覆盖上一个循环中的
    remoteNameDOM
    chatNameDOM
    元素

    或者您预先知道此“first”
    快照将始终包含一个文档(因为您的应用程序的体系结构),然后您可以通过如下方式处理第一个和唯一的元素来简化代码:

    roomRef.onSnapshot((snapshot) => {
        const roomDoc = snapshot.docChanges()[0];
        // ...
    
    });
    

    这回答了你的问题吗?很难说,因为代码是不可运行的(请参阅——您可以模拟API调用),但看起来循环中存在竞争条件。每次迭代都不等待下一次,它们都试图变异相同的DOM节点,因此这是不确定的。即使它是确定性的,也很难推断您的意图,因为最后一个元素对
    innerHTML
    的调用将覆盖之前的所有调用。您是否也可以发布Firestore文档的屏幕截图?这是否回答了您的问题?很难说,因为代码是不可运行的(请参阅——您可以模拟API调用),但看起来循环中存在竞争条件。每次迭代都不等待下一次,它们都试图变异相同的DOM节点,因此这是不确定的。即使它是确定性的,也很难推断您的意图,因为最后一个元素对
    innerHTML
    的调用将覆盖之前的所有调用。您能同时发布Firestore文档的屏幕截图吗?谢谢您的回复!我采纳了你的建议,将循环从a for each改为a for of。不幸的是,同样的问题仍然存在。@RichardDao您能添加
    console.log(snapshot.docChanges())
    并分享它记录了什么吗?谢谢您的回复!我采纳了你的建议,将循环从a for each改为a for of。不幸的是,同样的问题仍然存在。@RichardDao您能添加
    console.log(snapshot.docChanges())
    并分享它记录了什么吗?谢谢雷诺!关于获取第一个快照的最后一部分成功了!另外,你和@ggloren说得对,如果快照中有多个文档,我将覆盖我的元素。谢谢雷诺!关于获取第一个快照的最后一部分成功了!另外,你和@ggloren说得对,如果快照中有多个文档,我将覆盖我的元素。