Firestore query with subcollection

2020-03-31 07:26发布

Is this a limitation to Firebase or am I doing this all wrong? Everything works until I add the db.collection('users').doc(friendId).get()... in the middle of the code. Thanks in advance.

const db = admin.firestore();
const friendRef = db.collection('users').doc(id).collection('friends');

friendsList = [];

friendRef.get().then((onSnapshot) => {
    if (!onSnapshot.empty) {
        onSnapshot.forEach((friend) => {

            const friendId = String(friend.data().person_id);

            db.collection('users').doc(friendId).get().then((result) => {
                const firstName = String(result.data().name.first);
                const lastName = String(result.data().name.last);
            })

            const data = {
                personId: friendId,
                firstName: firstName,
                lastName: lastName,
            }

            friendsList.push(data);
        })

        res.send(friendsList);
    } else {
        res.send({
            'message': 'no friends'
        });
    }
}).catch((e) => {
    res.send({
        'error': e
    });
})

1条回答
乱世女痞
2楼-- · 2020-03-31 08:12

Data is loaded from Firestore asynchronously. This means that by the time you're sending the response back to the client, the data hasn't loaded from Firestore yet.

The easiest way to see this is with some well placed logging statements:

console.log("Before getting friend");
db.collection('users').doc(friendId).get().then((result) => {
  console.log("Got friend");
})
console.log("After getting friend");

When you run just this code it'll print:

Before getting friend

After getting friend

Got friend

That is probably not the order you expected the logs to be in. The reason is that the data may take some time to come back from Firestore. So instead of blocking the thread, it continues running the thread and then calls your callback function when the data is available. And that unfortunately means that your res.send(friendsList) ends up sending an empty list back to the client, since the data hasn't loaded yet.

The solution to this is to use a bunch of nested callbacks, to use Promise.all(), or ES6's new async/await keyword. With promises the code looks like this:

const db = admin.firestore();
const friendRef = db.collection('users').doc(id).collection('friends');

friendRef.get().then((onSnapshot) => {
  var promises = [];

  onSnapshot.forEach((friend) => {
    const friendId = String(friend.data().person_id);    
    promises.push(db.collection('users').doc(friendId).get());
  });

  Promise.all(promises).then((snapshots) => {
    friendsList = [];
    snapshots.forEach((result) => {
      friendsList.push({
        personId: result.id,
        firstName: result.data().name.first,
        lastName: result.data().name.last,
      });
    });
    res.send(friendsList);
  });
}).catch((e) => {
  res.send({
    'error': e
  });
})

So we first build a list of all friend read operations, then once all of those are done, we build the response and send it back.

查看更多
登录 后发表回答