Incorrect use of async in forEach loops

Loading

Incorrect Use of async in forEach Loops in JavaScript

Introduction

JavaScript provides the forEach method to iterate over arrays, but when combined with async/await, it does not behave as expected. This leads to issues like unhandled promises, unexpected execution order, and asynchronous functions not waiting properly. Understanding why this happens and how to fix it is crucial for writing reliable asynchronous code.


1. Understanding forEach and Asynchronous Code

What is forEach?

forEach is a higher-order function in JavaScript that allows iteration over an array. It takes a callback function as an argument and executes it for each array element.

const numbers = [1, 2, 3];

numbers.forEach(num => {
    console.log(num);
});

Output:

1
2
3
  • forEach executes synchronously.
  • It does not wait for asynchronous code inside the callback to complete.

2. Incorrect Use of async Inside forEach

Many developers assume that using async inside forEach will make the loop wait for each iteration to complete before moving to the next. However, forEach does not support await properly.

Example of Incorrect Usage

const fetchData = async (num) => {
    return new Promise(resolve => setTimeout(() => {
        console.log(`Fetching data for ${num}`);
        resolve();
    }, 1000));
};

const numbers = [1, 2, 3];

numbers.forEach(async (num) => {
    await fetchData(num);
    console.log(`Finished processing ${num}`);
});

Expected Output (What we assume will happen)

Fetching data for 1
Finished processing 1
Fetching data for 2
Finished processing 2
Fetching data for 3
Finished processing 3

Actual Output (What happens instead)

Fetching data for 1
Fetching data for 2
Fetching data for 3
Finished processing 1
Finished processing 2
Finished processing 3

What Went Wrong?

  1. forEach does not wait for the async function to resolve.
  2. The loop does not pause to let await complete before moving to the next iteration.
  3. This results in all API calls happening in parallel instead of sequentially.

3. Correct Ways to Handle async in Loops

Solution 1: Use a for...of Loop

Instead of forEach, use a for...of loop, which properly supports await.

const fetchData = async (num) => {
    return new Promise(resolve => setTimeout(() => {
        console.log(`Fetching data for ${num}`);
        resolve();
    }, 1000));
};

const processNumbers = async () => {
    const numbers = [1, 2, 3];

    for (const num of numbers) {
        await fetchData(num);
        console.log(`Finished processing ${num}`);
    }
};

processNumbers();

Output (As Expected)

Fetching data for 1
Finished processing 1
Fetching data for 2
Finished processing 2
Fetching data for 3
Finished processing 3
  • for...of waits for each await before moving to the next iteration.

Solution 2: Use map() with Promise.all()

If you want parallel execution instead of sequential execution, use .map() with Promise.all().

const fetchData = async (num) => {
    return new Promise(resolve => setTimeout(() => {
        console.log(`Fetching data for ${num}`);
        resolve();
    }, 1000));
};

const processNumbers = async () => {
    const numbers = [1, 2, 3];

    await Promise.all(numbers.map(async (num) => {
        await fetchData(num);
        console.log(`Finished processing ${num}`);
    }));
};

processNumbers();

Output (Parallel Execution)

Fetching data for 1
Fetching data for 2
Fetching data for 3
Finished processing 1
Finished processing 2
Finished processing 3
  • Promise.all() ensures that all async operations are executed in parallel.
  • ✅ The function waits for all operations to complete before proceeding.

Solution 3: Using a Traditional for Loop

A simple for loop also supports await correctly.

const numbers = [1, 2, 3];

async function processNumbers() {
    for (let i = 0; i < numbers.length; i++) {
        await fetchData(numbers[i]);
        console.log(`Finished processing ${numbers[i]}`);
    }
}

processNumbers();
  • ✅ Works sequentially like for...of
  • ✅ Fully supports await

4. Key Takeaways

Avoid using async inside forEach because:

  • forEach does not wait for async operations.
  • Execution order is not guaranteed.
  • Can cause unexpected behavior and bugs.

Use these alternatives instead:

  1. for...of → Best for sequential execution.
  2. map() + Promise.all() → Best for parallel execution.
  3. Traditional for loop → Works like for...of.

Leave a Reply

Your email address will not be published. Required fields are marked *