7

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});
Poorna Senani Gamage
  • 1,246
  • 2
  • 19
  • 30
  • 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 – Icepickle Mar 08 '18 at 12:15
  • yeah it's working fine...........thank you sooo much :) – Himanshu Prajapati Mar 08 '18 at 12:24
  • for (let category of categories) { service = await Service.count({category: category}); newcategories.push({ _id: category._id, name: category.name, count: service }); } – Himanshu Prajapati Mar 08 '18 at 12:25
  • you can use `reduce` to run this in series: `categories.reduce(async (previous, category) => { await previous; service = await Service.count... }, null);` – Thomas Mar 08 '18 at 12:30

2 Answers2

6

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();
Dominic
  • 62,658
  • 20
  • 139
  • 163
5

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

Icepickle
  • 12,689
  • 3
  • 34
  • 48