Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why javascript not wait for and of forEach and execute next line [duplicate]

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});
like image 612
Himanshu Prajapati Avatar asked Jan 28 '23 13:01

Himanshu Prajapati


2 Answers

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();
like image 140
Dominic Avatar answered Jan 31 '23 08:01

Dominic


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

like image 33
Icepickle Avatar answered Jan 31 '23 08:01

Icepickle