Tony Stark
Tony Stark

Reputation: 47

JAVASCRIPT : Empty array after foreach loop

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 ?

Upvotes: 3

Views: 1339

Answers (3)

libik
libik

Reputation: 23029

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');

Upvotes: 2

mahooresorkh
mahooresorkh

Reputation: 1444

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);

Upvotes: 1

samarth s
samarth s

Reputation: 65

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 : []
}

Upvotes: 0

Related Questions