Jeremy Gottfried
Jeremy Gottfried

Reputation: 1201

Does await inside a for loop block the main thread until the entire operation completes?

I am trying to assess whether await inside a for loop blocks the main thread until the entire operation is complete. It is unclear to me what occurs when await is inside a for loop:

for(const work of asyncWork) {
  await work;
}

Option 1:

  work1
  .then(work2)
  .then(work3)
  .then(work4)
  .then(work5)

Option 2:

  work1
  .then(continue);

  work2
  .then(continue);

  work3
  .then(continue);

  work4
  .then(continue);

  work5
  .then(continue);

Upvotes: 1

Views: 1320

Answers (1)

Jeremy Gottfried
Jeremy Gottfried

Reputation: 1201

Option 1 is the correct answer. The await inside the for loop forms a promise chain. To demonstrate, you need to execute two async functions, both containing a for loop that iterates through an array of promises. If you execute the code below, it logs out the two for loops concurrently:

Try running the snippet yourself:

function work(log) {
    return new Promise((resolve, reject) => {
        setTimeout(() => { console.log(log); resolve(); }, 2000)
    });
}

let allWork = [() => work(1), () => work(2), () => work(3), () => work(4)];

async function doWork() {
    for (const workItem of allWork) {
        await workItem();
        console.log("After await workItem()");
    }
    console.log("After for");
}

doWork();
doWork();
console.log("After doWork");

You will see:

After doWork

1

After await workItem()

1

After await workItem()

2

After await workItem()

2

After await workItem()

3

After await workItem()

3

After await workItem()

4

After await workItem()

After for

4

After await workItem()

After for

As you can see, the work is logged concurrently. The first for loop doesn't block the second from running to completion. 1 and After await workItem() get logged together because the javascript engine is smart enough to know that console.log doesn't need to be sent to the event loop. It can be inlined with the previous promise and executed synchronously.

There is no special magic happening here. Two javascript rules make this happen:

  1. await does not block for or while loops from running to completion.

  2. All await statements inside an async function are grouped together into a promise chain

Still unconvinced? The above code snippet behaves the same as the snippet below, which is a group of two promise chains:

function work(log) {
    return new Promise((resolve, reject) => {
        setTimeout(() => { console.log(log); resolve(); }, 2000)
    });
}

work(1)
.then(() => console.log('After await workItem()'))
.then(() => work(2))
.then(() => console.log('After await workItem()'))
.then(() => work(3))
.then(() => console.log('After await workItem()'))
.then(() => work(4))
.then(() => console.log('After await workItem()'))
.then(() => console.log("After for"))

work(1)
.then(() => console.log('After await workItem()'))
.then(() => work(2))
.then(() => console.log('After await workItem()'))
.then(() => work(3))
.then(() => console.log('After await workItem()'))
.then(() => work(4))
.then(() => console.log('After await workItem()'))
.then(() => console.log("After for"))
 
console.log("After doWork")

One caveat to keep in mind is this behavior only applies with for and while loops when it comes to array iteration. Array iterators like forEach do not form promise chains the same way. That's because each step in forEach is an execution of a function. The JS engine will not group awaits inside a nested async function call into an ancestral async function call.

Upvotes: 1

Related Questions