开发者问题收集

Firebase Firestore - Async/Await 在继续之前不等待获取数据吗?

2021-04-18
2909

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

我收到的错误是以下代码的第 10 行。我创建了一个 firestore 数据库,并尝试从 Collection 'rooms' 中监听并获取某个文档。我正在尝试从文档“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}`;
          })
        }
      }
    })
  })
})

但是,我收到了错误:

未捕获(在承诺中)TypeError:无法读取未定义的属性“joinerName”

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

remoteUser = await doc.data();
remoteNameDOM.innerHTML = `${remoteUser.joinerName} (Other)`;
chatNameDOM.innerHTML = `Chatting with ${remoteUser.joinerName}`;

我收到相同的错误。

我目前的理解是 await 将等待行/函数完成后再继续,因此在尝试调用它之前 remoteUser 不应为空。我会提到,有时代码运行良好,DOM 元素已更新,并且没有控制台错误。

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

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

userInfo Sub-Collection and Documents within userInfo

2个回答

您正在混合使用 async/awaitthen() ,这是不推荐的。我在下面提出了一种基于 Promise.all() 的解决方案,它有助于理解代码中涉及的不同数组。您可以按照 @Dharmaraj 的建议,使用 async/awaitfor-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) => {...})) )。如果多个 rooms 发生变化,则 snapshot.docChanges() 数组将包含多个更改,最后,您将在最后一个循环中 覆盖 remoteNameDOMchatNameDOM 元素。

或者您预先知道这个“第一个” 快照 将始终包含单个文档(由于应用程序的架构),然后您可以通过仅处理第一个和唯一元素来简化代码,如下所示:

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

});
Renaud Tarnec
2021-04-18

这里面有几个错误:

  1. db.collection() 不返回承诺,因此 await 在那里不是必需的
  2. forEach 忽略承诺,因此您实际上不能在 forEach 中使用 await。在这种情况下,for-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}`;
        })
      }
    }
  }
})
Dharmaraj
2021-04-18