Reputation: 1201
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
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:
await
does not block for
or while
loops from running to completion.
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