Async/await:

Absolutely! Let's walk through your code line by line with precise explanations, focusing especially on what happens after each await
and how microtasks are scheduled and executed.
✅ Code Recap:
jsCopyEditconsole.log("script start");
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
await null;
console.log("async2 after await");
}
async1();
Promise.resolve().then(() => {
console.log("promise 1");
});
queueMicrotask(() => {
console.log("microtask");
});
console.log("script end");
🔍 Step-by-step Execution in Detail
1. console.log("script start")
Directly executed in the call stack
Prints:
script start
2. Function Declarations
async1
andasync2
are defined.No code is executed yet—just registered in memory.
Let me explain why async1 end
comes after promise 1
and microtask
, which might seem counterintuitive at first.
Detailed Explanation of the Correct Order:
Initial Synchronous Execution (same as before):
script start
async1 start
async2
script end
Microtask Queue After Synchronous Execution:
The microtask queue contains (in this order):The continuation of
async2
(console.log("async2 after await")
)The
Promise.resolve().then
callback (console.log("promise 1")
)The
queueMicrotask
callback (console.log("microtask")
)(The
async1 end
is not here yet!)
Wait, why isn't async1 end
in the queue yet? Because of how await
works:
When
async2
hitsawait null
, it returns a promise and schedulesasync2 after await
as a microtask.The
await async2()
inasync1
then takes that promise and schedulesasync1 end
to run only after that promise resolves.
Processing Microtasks:
First microtask:
async2 after await
runsThis resolves the promise that
async2
returnedNow the
await async2()
inasync1
can continue, so it schedulesasync1 end
as a new microtask at the end of the queueCurrent queue: [
promise 1
,microtask
,async1 end
]
Next microtask:
promise 1
runsNext microtask:
microtask
runsFinally:
async1 end
runs
Why This Order Matters:
The key insight is that async1 end
gets scheduled as a microtask while the microtask queue is being processed, after async2 after await
runs. This makes it the last microtask to execute.
Visual Timeline:
Execution Phase | Output | Microtask Queue State |
Synchronous execution | script start | [] |
async1 start | [] | |
async2 | [] | |
script end | [async2 after await, promise 1, microtask] | |
Process microtasks: | ||
1. async2 after await | async2 after await | [promise 1, microtask, async1 end] |
2. promise 1 | promise 1 | [microtask, async1 end] |
3. microtask | microtask | [async1 end] |
4. async1 end | async1 end | [] |
Output:
script start
async1 start
async2
script end
async2 after await
promise 1
microtask
async1 end
Subscribe to my newsletter
Read articles from Vishal Pandey directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
