Asynchronous JavaScript:

Synchronous Javascript Execution:
Code:
javascriptCopyEditconsole.log("start");
const promise = new Promise((resolve, reject)=>{
console.log("1");
resolve("2");
})
promise.then((res)=>{
console.log(res);
})
console.log("end");
π Execution Flow with JS Runtime:
1. console.log("start")
Goes to call stack.
Prints:
start
Gets popped off.
2. Creating the Promise
jsCopyEditconst promise = new Promise((resolve, reject)=>{
console.log("1");
resolve("2");
})
Constructor function passed to
Promise
executes immediately.console.log("1")
goes to call stack β prints:1
.resolve("2")
is called β the value"2"
is stored, andthen()
callback is queued in the microtask queue.
3. promise.then(...)
jsCopyEditpromise.then((res)=>{
console.log(res);
})
Registers a
.then()
callback to be executed when the promise resolves.Since the promise is already resolved, this callback is put into the microtask queue.
4. console.log("end")
- Goes to call stack β prints:
end
.
π¦ Now the Call Stack is empty.
π§ Microtask Queue:
Now the event loop checks the microtask queue:
- It finds the
.then()
callback:
jsCopyEdit(res) => { console.log(res); }
This goes to call stack.
console.log("2")
executes β prints:2
Gets popped off.
β Final Output in Console:
sqlCopyEditstart
1
end
2
Asynchronous JavaScript Execution with Promises:
Let's break down how JavaScript executes these promises with their timeouts, explaining the call stack, event loop, and microtask queue.
const p1 = new Promise((resolve) => {
setTimeout(() => resolve('p1 resolved'), 10000); // 10 seconds
});
const p2 = new Promise((resolve) => {
setTimeout(() => resolve('p2 resolved'), 5000); // 5 seconds
});
function runPromises() {
p1.then(result => console.log(result));
p2.then(result => console.log(result));
}
runPromises();
Execution Flow Step-by-Step
1. Initial Execution Phase
Call Stack:
[main context]
Web APIs: None
Callback Queue: Empty
Microtask Queue: Empty
p1
Promise is created:The executor function runs immediately
setTimeout
is called (Web API)Timer (10s) starts in the background
Callback registered with Web APIs
p2
Promise is created:The executor function runs immediately
setTimeout
is called (Web API)Timer (5s) starts in the background
Callback registered with Web APIs
runPromises()
is called:p1.then()
registers a fulfillment handler (adds to microtask queue when resolved)p2.then()
registers a fulfillment handler (adds to microtask queue when resolved)
2. After 5 Seconds (p2 timeout completes)
Call Stack:
[main context]
(already empty, script finished)Web APIs: p1's timer still running (5s remaining)
Callback Queue:
[p2's setTimeout callback]
Microtask Queue: Empty
Event Loop Process:
The p2 timeout callback moves from Web APIs to Callback Queue
Event loop sees call stack is empty and moves callback to call stack
Callback executes:
resolve('p2 resolved')
- This puts p2's
.then()
handler into the microtask queue
- This puts p2's
Event loop prioritizes microtasks over other callbacks
Microtask executes:
console.log('p2 resolved')
runs
3. After 10 Seconds (p1 timeout completes)
Call Stack:
[main context]
(empty)Web APIs: All timers completed
Callback Queue:
[p1's setTimeout callback]
Microtask Queue: Empty
Event Loop Process:
The p1 timeout callback moves from Web APIs to Callback Queue
Event loop moves it to call stack
Callback executes:
resolve('p1 resolved')
- This puts p1's
.then()
handler into the microtask queue
- This puts p1's
Microtask executes:
console.log('p1 resolved')
runs
Key Observations
Order of Execution:
Even though we attached p1's handler first, p2 resolves first because its timeout is shorter
Output will be:
p2 resolved p1 resolved
Microtask Queue Priority:
Promise resolutions go to the microtask queue
The event loop processes all microtasks before moving to the next callback
Non-blocking Nature:
The main thread isn't blocked during the 10-second wait
Other code could execute during this time (though in this example there isn't any)
Visualization of the Process:
Time 0s:
- p1: setTimeout(10s) registered
- p2: setTimeout(5s) registered
- Handlers attached via .then()
Time 5s:
- p2's timeout completes
- p2's resolve callback executes
- p2's .then() handler added to microtask queue
- microtask executes: console.log('p2 resolved')
Time 10s:
- p1's timeout completes
- p1's resolve callback executes
- p1's .then() handler added to microtask queue
- microtask executes: console.log('p1 resolved')
This demonstrates how JavaScript handles asynchronous operations without blocking the main thread, using the event loop and task queues.
Asynchronous Execution with async/await
:
Async makes a function to return a promise.
Await makes an async function wait for a promise.
Allows writing asynchronous code in a synchronous manner.
Async function does not have resolve and reject parameter
Everything after the await keyword is placed in the event queue.
Let's analyze the same example but using async/await
syntax, which provides a more synchronous-looking way to handle promises.
Modified Example with async/await
:
const p1 = new Promise((resolve) => {
setTimeout(() => resolve('p1 resolved'), 10000); // 10 seconds
});
const p2 = new Promise((resolve) => {
setTimeout(() => resolve('p2 resolved'), 5000); // 5 seconds
});
async function runPromises() {
console.log(await p1);
console.log(await p2);
}
runPromises();
Key Differences from .then()
Approach
Execution Flow: Withβ£
await
, the function pauses at eachawait
until the promise settlesOrder Dependence: The promises are now processed sequentially rather than in parallel
Microtask Behavior: Each
await
yields control back to the event loop
Execution Flow Step-by-Step
1. Initial Execution Phase
Call Stack:
[main context]
Web APIs:
p1: 10s timer registered
p2: 5s timer registered
Callback Queue: Empty
Microtask Queue: Empty
2. runPromises()
Execution (Time 0s)
Function enters call stack
Hits
await p1
:The function pauses execution
Returns control to the event loop (call stack empties)
The rest of the function becomes a callback attached to p1's resolution
3. After 5 Seconds (p2 resolves)
Call Stack: Empty
Web APIs: p1's timer still running (5s remaining)
Callback Queue:
[p2's setTimeout callback]
Microtask Queue: Empty
But nothing happens with p2's resolution because:
No
.then()
handlers are attached directly to p2The
await p2
statement hasn't been reached yet (it's afterawait p1
)
4. After 10 Seconds (p1 resolves)
Call Stack:
[p1's setTimeout callback]
Web APIs: All timers completed
Callback Queue: Empty
Microtask Queue:
[continuation of runPromises]
Event Loop Process:
p1's timeout callback executes
resolve('p1 resolved')
This resumes the
runPromises
function at the firstawait
console.log('p1 resolved')
executesFunction proceeds to
await p2
:Since p2 already resolved at 5s, its value is immediately available
console.log('p2 resolved')
executes immediately
Final Output
p1 resolved
p2 resolved
Comparison with Original .then()
Version
Aspect | .then() Version | async/await Version |
Execution Order | Parallel (race) | Sequential |
Output Order | p2, p1 (by timeout) | p1, p2 (by await order) |
Timing | Both resolve independently | p2 waits for p1 to complete |
Resource Usage | More efficient | Less efficient |
Important Notes
Sequential Nature: Each
await
pauses the function until resolutionAlready-Resolved Promises: If you
await
a settled promise, it continues immediatelyError Handling: You can use
try/catch
withasync/await
More Efficient async/await
Pattern
If you want parallel execution with async/await
(like the original .then()
behavior):
javascript
Copy
Download
async function runPromisesParallel() {
const result1 = p1; // Don't await yet
const result2 = p2; // Don't await yet
console.log(await result1); // Now await both
console.log(await result2);
}
// Output order will depend on timeout durations
This version starts both promises immediately and awaits them later, similar to the original Promise.all behavior.
Q: I have a doubt that when p2's setTimeout callback was in the callback queue and p1's timer completed 10 sec, then p1's setTimeout callback should come in the callback queue, and now the callback queue should have p2's setTimeout callback and p1's setTimeout callback, and then it lets them execute one by one in the call stack, but you mentioned that once p1's timer completes, it directly goes to the call stack ?
You're absolutely right to question this! Let me clarify the exact sequence of events with more precision, because there was indeed an oversimplification in my previous explanation.
Corrected Detailed Execution Flow
Let's go through the timeline with proper attention to the callback queue mechanics:
Initial Setup (Time 0s)
Call Stack:
[main context]
β executes all synchronous codeWeb APIs:
p1: 10s timer registered
p2: 5s timer registered
Callback Queue: []
Microtask Queue: []
At 5s (p2's timer completes)
Web API moves p2's callback to the callback queue.
- Callback Queue:
[p2TimeoutCallback]
- Callback Queue:
Event Loop checks:
Call stack is empty (script has finished initial execution)
Moves p2's callback to call stack
Call stack executes
p2TimeoutCallback
:Calls
resolve('p2 resolved')
This adds p2's
.then()
handler to the Microtask QueueBut in our async/await example, we don't have explicit
.then()
!
Since we're using
await
, the resolution is handled differently:The promise resolution is noted internally
No microtask is queued yet because nothing is awaiting p2 at this point
At 10s (p1's timer completes)
Web API moves p1's callback to Callback Queue:
- Callback Queue:
[p1TimeoutCallback]
- Callback Queue:
Event Loop:
Call stack is empty
Moves p1's callback to call stack
Call stack executes
p1TimeoutCallback
:Calls
resolve('p1 resolved')
This resumes the paused
runPromises()
function:The
await p1
now completesconsole.log('p1 resolved')
executesThen hits
await p2
Checking p2's state:
p2 is already resolved (since 5s)
Immediately continues to
console.log('p2 resolved')
Key Clarifications
Callback Queue Behavior:
Both timer callbacks did go through the callback queue
They were executed in order of timer completion (p2 first, then p1)
But in our async/await example, p2's resolution was "orphaned" because nothing was actively awaiting it when it resolved
Critical Difference from .then():
// .then() version p1.then(...) // Attaches handler immediately p2.then(...) // Attaches handler immediately // Both can trigger independently // async/await version await p1 // Blocks until p1 resolves await p2 // Only reached after p1 resolves
What Really Happens with p2's Resolution:
At 5s: p2 resolves, but the
await p2
statement hasn't been reached yetThe promise resolution is stored internally
When execution finally reaches
await p2
at 10s+, it sees p2 is already resolved and continues immediately
Visual Timeline Correction
Time 0s:
- Timers started (p1:10s, p2:5s)
- runPromises() hits await p1 and pauses
Time 5s:
- p2's timeout completes
- Callback Queue: [p2TimeoutCallback]
- Event loop moves it to call stack
- p2 resolves ('p2 resolved')
- (No visible effect because nothing is awaiting yet)
Time 10s:
- p1's timeout completes
- Callback Queue: [p1TimeoutCallback]
- Event loop moves it to call stack
- p1 resolves ('p1 resolved')
- runPromises() resumes:
- logs 'p1 resolved'
- hits await p2 (already resolved)
- immediately logs 'p2 resolved'
This explains why you get
p1 resolved
p2 resolved
Instead of the parallel version's:
p2 resolved
p1 resolved
Subscribe to my newsletter
Read articles from Vishal Pandey directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
