9

when I create my api in nodejs and trying to pushing mongoose return count to new created array, it's not wait for the forEach and execute json.res() and giving null response. when I use setTimeout() then it's giving proper result.

let newcategories = [];
let service = 0;
const categories = await Category.find({}, '_id name');
categories.forEach(async (category) => {

service = await Service.count({category: category});

newcategories.push({ count:service });
console.log('newcategories is -- ', newcategories);

});  /* while executing this forEach it's not wait and execute res.json..*/


console.log('result --- ',result);
console.log('out newcategories is -- ', newcategories);
res.json({status: 200, data: newcategories});
4
  • 1
    Well, you cannot await the forEach, but the callback function is marked as async (so it will automatically return a Promise). So the forEach is long done before all your awaits are ready. Just change the forEach into a for let category of categories and await inside the for..of block Commented Mar 8, 2018 at 12:15
  • yeah it's working fine...........thank you sooo much :) Commented Mar 8, 2018 at 12:24
  • for (let category of categories) { service = await Service.count({category: category}); newcategories.push({ _id: category._id, name: category.name, count: service }); } Commented Mar 8, 2018 at 12:25
  • you can use reduce to run this in series: categories.reduce(async (previous, category) => { await previous; service = await Service.count... }, null); Commented Mar 8, 2018 at 12:30

2 Answers 2

8

So the problem you have is that async marked functions will return a promise per default, but that the Array.prototype.forEach method doesn't care about the result type of your callback function, it is just executing an action.

Inside your async function, it will properly await your responses and fill up your new categories, but the forEach loop on categories will be long gone.

You could either choose to convert your statements into a for .. of loop, or you could use map and then await Promise.all( mapped )

The for..of loop would be like this

for (let category of categories) {
  service = await Service.count({category: category});

  newcategories.push({ count:service });
  console.log('newcategories is -- ', newcategories);
}

the map version would look like this

await Promise.all( categories.map(async (category) => {
  service = await Service.count({category: category});

  newcategories.push({ count:service });
  console.log('newcategories is -- ', newcategories);
}));

The second version simply works because Promise.all will only resolve once all promises have completed, and the map will return a potentially unresolved promise for each category

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

Comments

7

You need to use map instead of forEach, to collect the awaits and wait for them to complete. Edit: Or you can use for..of which is pretty neat (thanks other ppl)!

const categories = ['a', 'b', 'c'];

function getNextCategory(oldCategory) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(String.fromCharCode(oldCategory.charCodeAt(0)+1));
    }, 1000);
  });
}

async function blah() {
  const categoryPromises = categories.map(getNextCategory);

  const nextCategories = await Promise.all(categoryPromises);

  console.log(nextCategories);
}

blah();

async function blah2() {
  const nextCategories = [];

  for (const category of categories) {
    nextCategories.push(await getNextCategory(category));
  };

  console.log(nextCategories);
}


blah2();

2 Comments

... or just do a "plain-old" for..of loop if you want to do serial requests.
Yeah I didn't actually know about for..of working for that before this so that's cool

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.