【问题标题】:Firebase Firestore - Async/Await Not Waiting To Get Data Before Moving On?Firebase Firestore - 异步/等待在继续之前不等待获取数据?
【发布时间】:2021-07-12 15:20:09
【问题描述】:

我是 JS 的“async/await”方面的新手,我正在尝试了解它是如何工作的。

我得到的错误是以下代码的第 10 行。我已经创建了一个 firestore 数据库,并试图从 Collection 'rooms' 中监听并获取某个文档。我正在尝试从 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: Cannot read property 'joinerName' of undefined

如果我将第 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 数据库的屏幕截图。我很感激这个建议。

【问题讨论】:

  • 这能回答你的问题吗? Using async/await with a forEach loop
  • 很难说,因为代码不可运行(请参阅minimal reproducible example——您可以模拟 API 调用),但看起来您在循环中有竞争条件。每次迭代都不会等待下一次,它们都试图改变相同的 DOM 节点,所以它是不确定的。即使它是确定性的,也很难推断出您的意图,因为最后一个元素对 innerHTML 的调用会覆盖之前的所有元素。
  • 您能否也发布您的 Firestore 文档的屏幕截图?

标签: javascript firebase asynchronous google-cloud-firestore async-await


【解决方案1】:

您正在混合使用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}`;
            })
        });

});

但是,我不清楚的是如何区分“第一”snapshot(即roomRef.onSnapshot((snapshot) => {...})))的不同元素。如果多个rooms 发生更改,snapshot.docChanges() 数组将包含多个更改,最后,您将在最后一个循环中覆盖remoteNameDOMchatNameDOM 元素。

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

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

});

【讨论】:

  • 谢谢雷诺!关于获取第一个快照的最后一部分成功了!另外,如果快照中有多个文档,您和@ggloren 是对的,我将覆盖我的元素。
【解决方案2】:

这里有几个错误:

  1. db.collection() 不返回承诺,因此在那里不需要等待
  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 的回复!我听取了您的建议,并将循环从 for-each 更改为 for-of。不幸的是,同样的问题仍然存在。
  • @RichardDao 你能添加console.log(snapshot.docChanges()) 并分享它记录了什么吗?
猜你喜欢
  • 1970-01-01
  • 2017-11-14
  • 2021-04-26
  • 2018-05-04
  • 2018-04-19
  • 2022-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多