Why does my async function return an empty array?

Viewed 2627

I'm trying to get the matches of my user by pushing them in an array and returning this array, so my router can send the data to the front-end. But I've got an issue with my async function: I just got an empty array. I've tried to put some breakpoints, and I noticed that my router sends the data before my service pushes the data to the array.

Here is my router code:

router.get("/allMatchs", auth, async (req, res) => {
  const user = await userService.getUserById(req);
  const matchs = await service.getMatchsByUser(user);
  res.send(matchs);
});

and there is my service code:

async function getMatchsByUser(user) {
  const userMatchs = user.matchs;
  let matchs;
  await userMatchs.map(async (m) => {
    let match = await Match.findById(m._id).select([
      "-isConfirmed",
      "-isUnmatched",
    ]);
    matchs.push(match);
  });
  return matchs;
}

Thank you for your help.

3 Answers

It's because .map() is not async aware. It doesn't wait for the promise that the callback returns. So, when you do this:

await userMatchs.map(...) 

The .map() returns an array. You are calling await on an array of promises (remember, .map() returns an array). That doesn't do anything useful. It doesn't wait for anything and the individual iterations inside the .map() didn't wait either.

You can either switch to plain for loop because a forloop is promise aware and it will await properly or you can use await Promise.all(userMatchs.map(...)) .

You could do this:

function getMatchsByUser(user) {
  return Promise.all(user.matchs.map((m) => {
    return Match.findById(m._id).select([
      "-isConfirmed",
      "-isUnmatched",
    ]));
  });
}

Or, if you want to do the requests to your database sequentially one at a time, use a plain for loop which await will work in:

async function getMatchsByUser(user) {
  let matchs = [];
  for (let m of user.matchs) {
    let match = await Match.findById(m._id).select([
      "-isConfirmed",
      "-isUnmatched",
    ]);
    matchs.push(match);
  }
  return matchs;
}

This code

userMatchs.map(async (m) => {
    let match = await Match.findById(m._id).select([
      "-isConfirmed",
      "-isUnmatched",
    ]);
    matchs.push(match);
  }); // -> return an array of promises

It returns an array of promises, so await will not wait for that to execute, -> return empty array.

For this, you should use Promise.all()

Something like this:

async function getMatchsByUser(user) {
  const userMatchs = user.matchs;
  let matches = await Promise.all(userMatchs.map(async (m) => {
    return Match.findById(m._id).select([
      "-isConfirmed",
      "-isUnmatched",
    ]);
  }));
  return matches;
}
Related