3

I'm working on a NestJSxMongoose store management project.

I have this piece of code in which I want to update some items in the database and put those updated items in an array that I will use later.

const updatedItems: Item[] = [];

purchaseData.items.forEach(async (purchasedItem) => {
  const itemInDB = await this.itemService.findItemByName(purchasedItem.name);

  itemInDB.quantity -= purchasedItem.quantity;

  const updatedItem = await this.itemService.updateItem(
    itemInDB['_id'],
    itemInDB
  );

  updatedItems.push(updatedItem);

  console.log(updatedItems); // Output : [{actual_data}]
});

console.log(updatedItems); // Output : []

The issue I have is that when I log the content of updatedItems inside the forEach(), it contains the actual expected data. But when I try to use it outside the forEach() loop, it logs an empty array. I want to use that array outside of the forEach().

What am I doing wrong ?

lpizzinidev
  • 12,741
  • 2
  • 10
  • 29
Tony Stark
  • 43
  • 1
  • 9
  • 1
    `await` not works on `forEach` use `for` or `for of`, more details [here](https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop?rq=1) – 1sina1 Jul 12 '22 at 12:31
  • @1sina1 , Oh okay that's what I did not know. I will try to use for or for of from now on when working with async operations. I got a better understanding now, thank you ! – Tony Stark Jul 12 '22 at 15:29

3 Answers3

2

If you want to wait for results, you have to write it like this: Get the array of all promises and awaits them.

.forEach (as well as .map) runs synchronously. If you pass async function as a parameter, it does not await them, it just executes them to the nearest await and then returns (still unresolved) promise.

Therefore the console.log(updatedItems); // Output : [] is executed before the logic inside async function.

const updatedItems: Item[] = [];

const promises = purchaseData.items.map(async (purchasedItem) => {
  const itemInDB = await this.itemService.findItemByName(purchasedItem.name);

  itemInDB.quantity -= purchasedItem.quantity;

  const updatedItem = await this.itemService.updateItem(
    itemInDB['_id'],
    itemInDB
  );

  updatedItems.push(updatedItem);

  console.log(updatedItems); // Output : [{actual_data}]
});

await Promise.all(promises);

console.log(updatedItems); // Output : []

Here you can see the order of execution:

const purchaseData = { items: [1,2] }

purchaseData.items.forEach(async (purchasedItem) => {
  console.log('first', purchasedItem);
  await console.log(); // the awaiting console log does nothing, it is just to trigger the beaviour of async function when it hits await
  console.log('third', purchasedItem);
});

console.log('second');
libik
  • 22,239
  • 9
  • 44
  • 87
  • Oh right, thank you for taking the time to explain. Now I have a better understanding of how async operations work. I tried the solution you provided and it's now working as expected, thanks ! – Tony Stark Jul 12 '22 at 15:31
1

forEach expects a synchronous function. If an asynchronous callback is passed to it, the synchronous parts of the program (here, your second console.log) will be executed before executing the callback awaited lines. Pay attention to this link.

It is better to put the whole iterative parts in an asynchronous function and use a for loop instead of forEach inside it, and then write the console.log after calling the asynchronous function.

const updatedItems: Item[] = [];

async function updateData(){
    for (let index = 0; index < purchaseData.items.length; index++) {
       const itemInDB = await this.itemService.findItemByName(purchaseData.items[index].name);

       itemInDB.quantity -= purchasedItem.quantity;

       const updatedItem = await this.itemService.updateItem(
           itemInDB['_id'],
           itemInDB
       );

       updatedItems.push(updatedItem);

       console.log(updatedItems);
   }
}

await updateData();
console.log(updatedItems);
mahooresorkh
  • 1,361
  • 2
  • 8
  • 16
0

Use Async and await its not waiting till the loop its going to next you need to wait until the loop finish

async function updateItemFun() {
const updatedItems: Item[] = [];

await purchaseData.items.forEach(async (purchasedItem) => {
  const itemInDB = await this.itemService.findItemByName(purchasedItem.name);

  itemInDB.quantity -= purchasedItem.quantity;

  const updatedItem = await this.itemService.updateItem(
    itemInDB['_id'],
    itemInDB
  );

  updatedItems.push(updatedItem);

  console.log(updatedItems); // Output : [{actual_data}]
});

console.log(updatedItems); // Output : []
}
samarth s
  • 65
  • 6