Async/await:

Vishal PandeyVishal Pandey
3 min read

Absolutely! Let's walk through your code line by line with precise explanations, focusing especially on what happens after each awaitand 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 and async2 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:

  1. Initial Synchronous Execution (same as before):

    • script start

    • async1 start

    • async2

    • script end

  2. 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 hits await null, it returns a promise and schedules async2 after await as a microtask.

  • The await async2() in async1 then takes that promise and schedules async1 end to run only after that promise resolves.

  1. Processing Microtasks:

    • First microtask: async2 after await runs

      • This resolves the promise that async2 returned

      • Now the await async2() in async1 can continue, so it schedules async1 end as a new microtask at the end of the queue

      • Current queue: [promise 1, microtask, async1 end]

    • Next microtask: promise 1 runs

    • Next microtask: microtask runs

    • Finally: 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 PhaseOutputMicrotask Queue State
Synchronous executionscript start[]
async1 start[]
async2[]
script end[async2 after await, promise 1, microtask]
Process microtasks:
1. async2 after awaitasync2 after await[promise 1, microtask, async1 end]
2. promise 1promise 1[microtask, async1 end]
3. microtaskmicrotask[async1 end]
4. async1 endasync1 end[]

Output:

script start
async1 start
async2
script end
async2 after await
promise 1
microtask
async1 end
0
Subscribe to my newsletter

Read articles from Vishal Pandey directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vishal Pandey
Vishal Pandey