2

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.

Jasperan
  • 2,154
  • 1
  • 16
  • 40
  • It's because `.map()` is not `async` aware. It doesn't wait for the promise that the callback returns. You can either switch to plain `for` loop because a `for `loop is promise aware and it will `await` properly or you can use `await Promise.all(userMatchs.map(...))` . – jfriend00 May 14 '20 at 01:52
  • FYI, it's kind of silly to use `.map()`, but still be manually doing `matchs.push(match)`. You use `.map()` when you WANT the returned array from `.map()` which you are completely ignoring. Otherwise, just use a `for` loop. – jfriend00 May 14 '20 at 01:54
  • Maybe you didn't see, but I put a much more extended version of this information in my answer including a couple different implementation choices. – jfriend00 May 14 '20 at 02:08

3 Answers3

4

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;
}
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • "It's because .map() is not async aware" ... F.....K! haven't found this anywhere on the internet until seeing this answer. Learning TS this way is not so friendly lol – Enrique Jun 06 '23 at 23:08
0

Your problem is userMatchs.map

Using async/await combined with map() can be a little tricky

How to use Async and Await with Array.prototype.map()

LiuXiMin
  • 1,225
  • 8
  • 17
0

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;
}
Tan Dat
  • 2,888
  • 1
  • 17
  • 39