How to wait for a function to finish with all its inside expressions, in router in mongoose?

Issue

I just want to change the value of a key of all the objects inside an array

What I want actually –

The object which I queried from the database is –

{
  _id: 61389277fa5c742caf959885,
  title: 'What is GRE?',
  forumTab: 'GRE',
  askedAt: 2021-09-08T10:37:43.979Z,
  askedBy: {
    _id: 60f0a6a9b4259f7ef9c49cc8,
  }
}

I want to add more key-value pairs in the askedBy key by again querying the database for the User with the given _id

Now, the user object which is queried is –

{
  role: 'student',
  _id: 60f0a6a9b4259f7ef9c49cc8,
  firstName: 'Rishav',
  lastName: 'Raj'
}

Finally I want to return the below object in response –

{
  _id: 61389277fa5c742caf959885,
  title: 'What is GRE?',
  forumTab: 'GRE',
  askedAt: 2021-09-08T10:37:43.979Z,
  askedBy: {
    _id: 60f0a6a9b4259f7ef9c49cc8,
    role: 'student',
    firstName: 'Rishav',
    lastName: 'Raj'
  }
}

I am creating a new array questionsToSend and pushing the object with updated key-value pairs which I am getting after querying the database for each elements in the questions array, I have created functions for respective query that I need to render in sequence, even after rendering the functions in proper sequence why the new array questionsToSend is not populating with the objects before returning the response?

router.get("/questions", async (req, res) => {
  if (!req.query.forumTab) return res.status(400).send("something went wrong");

  const page = parseInt(req.query.page) - 1;
  const perPage = parseInt(req.query.perPage);

  let questionsToSend = [];

  const func0 = async (callback) => {
    const questions = await Question.find({ forumTab: req.query.forumTab })
      .sort({ askedAt: -1 })
      .limit(perPage)
      .skip(perPage * page);
    console.log("xxxxxxx");
    callback(questions);
  };

  const func1 = async (questions, callBack) => {
    questions.forEach(async (question) => {
      const askedUserData = await User.findById(question.askedBy._id);

      if (!askedUserData) {
        const index = questions.indexOf(question);
        questions.splice(index, 1);
        return;
      }

      questionsToSend.push({
        ..._.pick(question, [
          "_id",
          "title",
          "askedAt",
          "tags",
        ]),
        askedUserData,
      });
      console.log(questionsToSend);
    });
    console.log("yyyyyyyy");
    callBack();
  };

  func0(
    (questions) =>
      func1(questions, async () => {
        console.log("zzzzzzzz");
        res.status(200).send(questionsToSend);
      })
  );
});

Solution

We can use aggregation to achieve this

Question.aggregate([
 {
   $match: { forumTab: req.query.forumTab } 
 }, 
 { 
    $lookup: { 
      from: 'users',
      localField: 'askedBy._id',
      foreignField: '_id',
      as: "user"
    } 
 },
 { $unwind: "$user"},
 { "$addFields": {
    "askedBy": {
      "$mergeObjects": ["$askedBy", "$user"]
     }
    }
  },
  { $project: { "user" : 0} },
  { $sort: {"askedAt": -1}},
  { $skip: perPage * page},
  { $limit: perPage},
])
  1. $match is used to apply filter
  2. $lookup is used to do a join on a collection. I have assumed the collection name is users.
  3. $lookup returns the matched result as an array. Converting it to object using $unwind since we get only one back.
  4. $addFields with $mergeObjects is merging the existing askedBy field and newly user field
  5. Removing the user field from the result set with $project.
  6. And then sort, skip and limit.

Answered By – Kannan.dev

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published