0

Building a NodeJS REST API. Trying to send load data from FireBase collection, then sending it to the user (as API response). Looks like the problem is that it's not waits for the firebase fetch to resolve, but send back a response without the collection data. (tried to use ASYNC-AWAIT but its not working)

exports.getChatMessages = async (req, res, next) => {
  const chatId = req.params.chatId
  const getChatData = () => {
    db
      .collection('chats')
      .doc(chatId)
      .collection('messages')
      .orderBy('timeStamp', 'asc')
      .onSnapshot((snapshot) => {
        snapshot.docs.forEach(msg => {
          console.log(msg.data().messageContent)
          return {
            authorID: msg.data().authorID,
            messageContent: msg.data().messageContent,
            timeStamp: msg.data().timeStamp,
          }
        })
      })
  }
  try {
    const chatData = await getChatData()
    console.log(chatData)
    res.status(200).json({
      message: 'Chat Has Found',
      chatData: chatData
    })
  } catch (err) {
    if (!err.statusCode) {
      err.statusCode(500)
    }
    next(err)
  }
}

As you can see, I've used 2 console.logs to realize what the problem, Terminal logs looks like:

  • [] (from console.logs(chatData))
  • All messages (from console.log(msg.data().messageContent))

Is there any way to block the code unti the firebase data realy fetched?

0

3 Answers 3

2

If I correctly understand, you want to send back an array of all the documents present in the messages subcollection. The following should do the trick.

  exports.getChatMessages = async (req, res, next) => {
    const chatId = req.params.chatId;
    const collectionRef = db
      .collection('chats')
      .doc(chatId)
      .collection('messages')
      .orderBy('timeStamp', 'asc');

    try {
      const chatsQuerySnapshot = await collectionRef.get();
      const chatData = [];
      chatsQuerySnapshot.forEach((msg) => {
        console.log(msg.data().messageContent);
        chatData.push({
          authorID: msg.data().authorID,
          messageContent: msg.data().messageContent,
          timeStamp: msg.data().timeStamp,
        });
      });
      console.log(chatData);
      res.status(200).json({
        message: 'Chat Has Found',
        chatData: chatData,
      });
    } catch (err) {
      if (!err.statusCode) {
        err.statusCode(500);
      }
      next(err);
    }
  };

The asynchronous get() method returns a QuerySnapshot on which you can call forEach() for enumerating all of the documents in the QuerySnapshot.

Sign up to request clarification or add additional context in comments.

2 Comments

That's the full example of what I also explained (with a bit of update on the structure). If that doesn't do the trick, then there's something wrong elsewhere, as the code seems pretty well to me (regardless of where the data manipulation is done).
It works!, updated the code with some promises (which i could use await on), changed the map method to a forEach and it succedded. @Renaud's code looks better then mine, always good to learn from advanced developers, thank you guys!
1

You can only await a Promise. Currently, getChatData() does not return a Promise, so awaiting it is pointless. You are trying to await a fixed value, so it resolves immediately and jumps to the next line. console.log(chatData) happens. Then, later, your (snapshot) => callback happens, but too late.

const getChatData = () => new Promise(resolve => { // Return a Promise, so it can be awaited
    db.collection('chats')
        .doc(chatId)
        .collection('messages')
        .orderBy('timeStamp', 'asc')
        .onSnapshot(resolve) // Equivalent to .onSnapshot((snapshot) => resolve(snapshot))
})


const snapshot = await getChatData();
console.log(snapshot)

// Put your transform logic out of the function that calls the DB. A function should only do one thing if possible : call or transform, not both.
const chatData = snapshot.map(msg => ({
    authorID: msg.data().authorID,
    messageContent: msg.data().messageContent,
    timeStamp: msg.data().timeStamp,
}));

res.status(200).json({
    message: 'Chat Has Found',
    chatData
})

2 Comments

Updated the code, but still the same problem :(
It can't be. My code is radically different from yours. You can't get the same error.
0

Right now, getChatData is this (short version):

const getChatData = () => {
  db
    .collection('chats')
    .doc(chatId)
    .collection('messages')
    .orderBy('timeStamp', 'asc')
    .onSnapshot((snapshot) => {}) // some things inside
}

What that means is that the getChatData function calls some db query, and then returns void (nothing). I bet you'd want to return the db call (hopefully it's a Promise), so that your await does some work for you. Something along the lines of:

const getChatData = async () => 
  db
    .collection('chats')
    // ...

Which is the same as const getChatData = async() => { return db... }


Update: Now that I've reviewed the docs once again, I see that you use onSnapshot, which is meant for updates and can fire multiple times. The first call actually makes a request, but then continues to listen on those updates. Since that seems like a regular request-response, and you want it to happen only once - use .get() docs instead of .onSnapshot(). Otherwise those listeners would stay there and cause troubles. .get() returns a Promise, so the sample fix that I've mentioned above would work perfectly and you don't need to change other pieces of the code.

4 Comments

It's not a Promise, since it takes a callback function (.onSnapshot((snapshot) => {)
Yeah I saw that. I don't think that's needed here, as it returns a response, so it should be a single-time request to FireBase.
Changed the get, add a return statement and used Async Function for getChatDat(), still not working, still looks like the getChatData() is not blocking the execution of the other code and console.log(chatData) is executed before it.. :(
There's no way for us to help you if you don't update the code sample :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.