JavaScript developers know that understanding the inner workings of Promises is essential, especially when it comes to handling asynchronous code effectively. Mastering Promises can make the difference between writing efficient, non-blocking code and code that’s riddled with bugs and difficult to maintain. In senior-level interviews, you’re likely to encounter questions designed to probe your understanding of the JavaScript event loop and microtasks. Let’s look at a complex Promise-based question similar to the one in the example above and break it down step-by-step to understand how it executes.
const promise = new Promise((resolve, reject) => {
console.log("a");
setTimeout(() => {
console.log("timeout");
resolve("resolved");
console.log("end timeout");
}, 0);
console.log("b");
});
promise.then((result) => {
console.log(result);
});
console.log("c");
Understanding the Code Step-by-Step
Let’s break down this code line by line and see what’s happening under the hood:
1. Promise Instantiation
The Promise
is created with the following code:
const promise = new Promise((resolve, reject) => {
console.log("a");
// ...
console.log("b");
});
Creating a Promise immediately executes the function provided to its constructor. This is a synchronous operation, meaning the code inside the Promise runs right away, before any asynchronous code such as setTimeout
or then
.
Console Output at this Stage:
console.log("a");
logsa
console.log("b");
logsb
So, so far, the output is:
a
b
2. The setTimeout Function
setTimeout(() => {
console.log("timeout");
resolve("resolved");
console.log("end timeout");
}, 0);
This setTimeout
callback is pushed to the macrotask queue (or the “task queue”), which executes after the main thread has finished processing all synchronous code. Since the timer delay is set to 0
, the callback will be placed on the macrotask queue and executed as soon as possible after the main thread is done.
Notice that this section of code has two console.log
statements:
console.log("timeout");
(will logtimeout
)console.log("end timeout");
(will logend timeout
after resolving the Promise)
Key Point: The resolve("resolved")
statement doesn’t cause the callback passed to then
to run immediately. It simply moves the Promise’s then
callback to the microtask queue, which gets priority over the macrotask queue once the main script finishes executing.
3. Attaching then
promise.then((result) => {
console.log(result);
});
When we attach a then
to a Promise, the callback provided to then
will run asynchronously after the Promise is resolved. However, it’s added to the microtask queue, which executes immediately after the current call stack is empty but before any macrotask.
4. Logging c
console.log("c");
This line executes immediately after the Promise and setTimeout
setup because it’s outside any asynchronous code.
At this point, the output log is:
a
b
c
The Event Loop
When the main code finishes running, the event loop checks the microtask queue before moving to the macrotask queue. Here’s the sequence the event loop follows in our example:
- The main script (
a
,b
,c
) executes first. - The microtask queue is checked next, but it’s empty at this point because the Promise hasn’t been resolved yet.
- The macrotask queue is checked and executes
setTimeout
’s callback:- Logs
timeout
- Resolves the Promise with
"resolved"
, pushing thethen
callback to the microtask queue - Logs
end timeout
- Logs
- The event loop finishes the current macrotask (
setTimeout
) and goes back to the microtask queue.- Executes
console.log(result)
with"resolved"
as the argument.
- Executes
Final Output and Order
Putting it all together, here’s the full order of output:
a
b
c
timeout
end timeout
resolved
Key Takeaways from This Code
- Promises are synchronous by default: Code inside a
Promise
executor function runs immediately. - Microtasks vs. Macrotasks: Microtasks (like
Promise.then
) take precedence over macrotasks (likesetTimeout
), so they will execute as soon as the main script completes. - The Event Loop: It’s essential to understand the event loop in JavaScript to predict the order of output accurately. The event loop first empties the call stack, then processes the microtask queue, and finally moves to the macrotask queue.
Practice Questions
To solidify your understanding, try answering these similar questions and follow the logic above to predict their outputs:
Question 1
const promise = new Promise((resolve) => {
console.log("x");
resolve("y");
});
promise.then((res) => {
console.log(res);
});
console.log("z");
Question 2
console.log("start");
setTimeout(() => {
console.log("setTimeout");
}, 0);
Promise.resolve().then(() => {
console.log("promise1");
}).then(() => {
console.log("promise2");
});
console.log("end");
Working through these examples will deepen your understanding of how JavaScript handles asynchronous operations with Promises and the event loop. Senior-level questions often focus on these subtleties, so knowing this well could make a big difference in your interview performance!
Comment me below!!