BennyVenassi
BennyVenassi

Reputation: 15

node.js/express/mongoose combine async function loop in a async function

I'm using Node.js and mongoose to fetch data. Before sending the result i want to loop through the data and fetch some additional informations of a another mongoose collection.

I don't get it to manage, that the first mongoose call waits, that the other promises are finished. I assume that my mixture of promises and .then calls is a mess... :S

My Code:

exports.checkChats = (req, res, next) => {
console.log(req.userData.userId);
let modifiedChats = [];
let partner;
Messages.find({ $or: [{ "users.starter": req.userData.userId }, { "users.partner": req.userData.userId }] })
    .then(chats => {
        if (chats) {
            console.log('Start');
            const promises = chats.map(async chat => {
                if (chat.users.starter.toString() === req.userData.userId.toString()) {
                    console.log('fetch partnerData for partner ' + chat.users.partner);
                    partner = chat.users.partner;
                }
                if (chat.users.partner.toString() === req.userData.userId.toString()) {
                    console.log('fetch partnerData for starter ' + chat.users.starter);
                    partner = chat.users.starter;
                }
                User.findById(partner).then(fetchedPartner => {
                    console.log('Partner fetched: ', fetchedPartner.userName);
                    modifiedChats.push({ id: chat._id, status: chat.status, lastMessage: chat.lastMessage, partnerName: fetchedPartner.userName, partnerImg: fetchedPartner.imagePath });
                });
            });
            Promise.all(promises).then(() => {
                console.log('End');
                res.status(201).json({
                    message: 'Chats found',
                    chats: modifiedChats
                });
            });
        }
    }).catch(error => {
        res.status(500).json({
            message: 'Couldnt fetch chats!',
            error: error
        });
    });
};

And the Terminallog:

Start
fetch partnerData for partner 5eb8502aad51b72012a2ccd1
fetch partnerData for partner 5eb84c93ad51b72012a2cc56
End
Partner fetched:  Summer
Partner fetched:  Pepe

Thank you guys.

Upvotes: 0

Views: 35

Answers (1)

zishone
zishone

Reputation: 1244

Hi so the problem in your code is located here

const promises = chats.map(async chat => {
  if (chat.users.starter.toString() === req.userData.userId.toString()) {
    console.log('fetch partnerData for partner ' + chat.users.partner);
    partner = chat.users.partner;
  }
  if (chat.users.partner.toString() === req.userData.userId.toString()) {
    console.log('fetch partnerData for starter ' + chat.users.starter);
    partner = chat.users.starter;
  }
  User.findById(partner).then(fetchedPartner => {
    console.log('Partner fetched: ', fetchedPartner.userName);
    modifiedChats.push({ id: chat._id, status: chat.status, lastMessage: chat.lastMessage, partnerName: fetchedPartner.userName, partnerImg: fetchedPartner.imagePath });
  });
});

Every iteration of this loop calls User.findById(partner) which returns a promise and is asynchronous. The function does not know that it needs to wait for its result. One way to tell the function to wait for User.findById(partner) to finish is by adding return.

return User.findById(partner).then(fetchedPartner => {
    console.log('Partner fetched: ', fetchedPartner.userName);
    modifiedChats.push({ id: chat._id, status: chat.status, lastMessage: chat.lastMessage, partnerName: fetchedPartner.userName, partnerImg: fetchedPartner.imagePath });
  });

You can also remove the async in

const promises = chats.map(async chat => {

It serves no purpose, because you are not utilizing async/await anyway.


Here's also an example to refactor your code using async/await.

exports.checkChats = async (req, res, next) => { // by putting async here, you are enabling a function to use await
  try {
    console.log(req.userData.userId);
    let partner;
    const chats = await Messages.find({ $or: [{ "users.starter": req.userData.userId }, { "users.partner": req.userData.userId }] }); // by putting await before a function call the returns a promise, you will have the access to the value it will return by this syntax
    console.log('Start');
    const promises = chats.map(async chat => { // by putting async here, you are enabling a function to use await; This is also a function so you will need to put async here if you want to use await inside
      if (chat.users.starter.toString() === req.userData.userId.toString()) {
        console.log('fetch partnerData for partner ' + chat.users.partner);
        partner = chat.users.partner;
      }
      if (chat.users.partner.toString() === req.userData.userId.toString()) {
        console.log('fetch partnerData for starter ' + chat.users.starter);
        partner = chat.users.starter;
      }
      const fetchedPartner = await User.findById(partner); // same here, I put await because its a promise
      console.log('Partner fetched: ', fetchedPartner.userName);
      return { id: chat._id, status: chat.status, lastMessage: chat.lastMessage, partnerName: fetchedPartner.userName, partnerImg: fetchedPartner.imagePath }; // I am just returning the values here. By the end of the .map() it will have created an array of promises that each resolves the values return here
    });
    const modifiedChats = await Promise.all(promises); // this will convert the array of promises to an array of the values that was resolve  by each of the promises.
    console.log('End');
    res.status(201).json({
      message: 'Chats found',
      chats: modifiedChats
    });
  } catch (error) { // using try catch is the way for catching thrown errors / rejected promises
    res.status(500).json({
      message: 'Couldnt fetch chats!',
      error: error
    });
  }
};

Upvotes: 1

Related Questions