DEV Community

Emily Scott
Emily Scott

Posted on

Why JavaScript forEach() Does Not Work with await (How to Fix It)

Why JavaScript forEach() Does Not Work with await (And How to Fix It)

One of the most confusing async problems in JavaScript happens when developers use await inside forEach() and expect everything to run in order.

It looks correct.

It feels correct.

But it breaks in production.

This issue appears often in API requests, database operations, email sending, payment processing, and file uploads.

Many developers lose hours debugging this.

Let’s fix it properly.


The Problem

Suppose you want to process users one by one.

You write this:

const users = ["John", "Emma", "Michael"];

users.forEach(async (user) => {
  await sendEmail(user);
  console.log(`Email sent to ${user}`);
});

console.log("All emails processed");
Enter fullscreen mode Exit fullscreen mode

At first glance, this looks perfect.

You expect:

  1. Send email to John
  2. Send email to Emma
  3. Send email to Michael
  4. Print final message

Expected output:

Email sent to John
Email sent to Emma
Email sent to Michael
All emails processed
Enter fullscreen mode Exit fullscreen mode

But the actual output is often:

All emails processed
Email sent to John
Email sent to Emma
Email sent to Michael
Enter fullscreen mode Exit fullscreen mode

Sometimes even worse—execution becomes unpredictable.

Very frustrating.


Why This Happens

The problem is simple:

forEach() does not wait for async/await.

It completely ignores Promises.

Even if you write await inside it, forEach() itself does not pause.

It immediately starts all iterations and moves on.

That means this line:

console.log("All emails processed");
Enter fullscreen mode Exit fullscreen mode

runs before the async work finishes.

This creates race conditions and broken logic.

Especially dangerous in production systems.


Understanding the Core Issue

Let’s simplify it.

This:

users.forEach(async (user) => {
  await sendEmail(user);
});
Enter fullscreen mode Exit fullscreen mode

does NOT mean:

“Wait for each task one by one”

It actually means:

“Start everything immediately and do not wait”

That is the real problem.

forEach() was designed for synchronous operations—not async control flow.


Wrong Real-World Example

Imagine payment processing:

orders.forEach(async (order) => {
  await chargeCustomer(order);
});

closeDailyReport();
Enter fullscreen mode Exit fullscreen mode

Danger:

The report may close before payments finish.

That can create serious business problems.

This is not a small bug.

It can affect money.


The Correct Fix: Use for...of

The safest solution is for...of.

Like this:

const users = ["John", "Emma", "Michael"];

async function processUsers() {
  for (const user of users) {
    await sendEmail(user);
    console.log(`Email sent to ${user}`);
  }

  console.log("All emails processed");
}

processUsers();
Enter fullscreen mode Exit fullscreen mode

Now JavaScript waits properly.

Output:

Email sent to John
Email sent to Emma
Email sent to Michael
All emails processed
Enter fullscreen mode Exit fullscreen mode

Perfect.

Predictable.

Safe.


Why for...of Works

Because for...of respects await.

Each loop waits for the previous one to finish.

This is called sequential execution.

Very useful for:

  • Payment processing
  • Database writes
  • Email sending
  • File uploads
  • Rate-limited APIs
  • Authentication flows

Anywhere order matters.


What If You Want Parallel Execution?

Sometimes you do want everything to run together.

For example:

  • Fetching multiple products
  • Loading dashboard widgets
  • Getting multiple API responses

In that case, use Promise.all().

Example:

const users = ["John", "Emma", "Michael"];

await Promise.all(
  users.map(async (user) => {
    await sendEmail(user);
    console.log(`Email sent to ${user}`);
  })
);

console.log("All emails processed");
Enter fullscreen mode Exit fullscreen mode

This runs tasks in parallel—but still waits for all of them to finish.

Very powerful.


Rule to Remember

Use this simple rule:

Need order?

Use for...of

Need speed?

Use Promise.all()

Never use?

forEach() with await

This rule saves a lot of debugging time.


Another Common Mistake

Developers often write this:

await users.forEach(async (user) => {
  await sendEmail(user);
});
Enter fullscreen mode Exit fullscreen mode

This still does NOT work.

Why?

Because forEach() returns undefined.

There is nothing real for await to wait for.

So even this fails.

Very common mistake.


Debugging Question

Whenever async loops behave strangely, ask:

“Am I awaiting the loop… or only the function inside it?”

That question reveals the bug fast.

Most developers miss this.


Final Thoughts

forEach() is excellent for simple synchronous loops.

But for async workflows, it becomes dangerous.

Remember:

  • forEach() ignores await
  • for...of handles sequence safely
  • Promise.all() handles parallel work properly
  • await forEach() is still wrong

Understanding this makes JavaScript async code much cleaner.

And production bugs become much easier to prevent.


Your Turn

Have you ever used await inside forEach() and spent hours debugging it?

Almost every JavaScript developer has.

Peace,
Emily Idioms

Top comments (0)