Conquering Asynchronicity: Mastering JavaScript Promises

Ankit Gupta
3 min readJan 17, 2025

--

JavaScript developers need to understand how Promises work, especially when dealing with asynchronous code. This knowledge is crucial for writing efficient and bug-free code. In job interviews, you might be asked questions about Promises, the JavaScript event loop, and how they interact.

Let’s explore a complex Promise-based scenario and dissect its execution step-by-step to solidify your understanding.

Understanding the Code Step-by-Step

  1. Promise Creation and Initial Execution
const promise = new Promise((resolve, reject) => {
console.log("x");
setTimeout(() => {
console.log("setTimeout");
resolve("resolved");
console.log("end timeout");
}, 0);
console.log("y");
});
  1. Promise Creation: This line creates a new Promise object.
  2. Executor Function: The provided function (with resolve and reject) is immediately executed.
  3. Synchronous Output:
  • console.log("x"); logs "x" to the console.
  • console.log("y"); logs "y" to the console.

2. The setTimeout Function and Macrotasks

setTimeout(() => {
console.log("setTimeout");
resolve("resolved");
console.log("end timeout");
}, 0);
  • Scheduling: setTimeout schedules the provided callback function to be executed after 0 milliseconds.
  • Macrotask Queue: This callback is placed in the macrotask queue (also known as the task queue).
  • Asynchronous Execution: Macrotasks are executed after the main thread (the synchronous part of the code) has finished.

3. Attaching then and Microtasks

promise.then((result) => {
console.log(result);
});
  • then Method: This method attaches a callback function to the Promise.
  • Microtask Queue: When the Promise is resolved, this callback function is added to the microtask queue.
  • Microtask Priority: Microtasks have higher priority than macrotasks.
console.log("e");
  • Synchronous Execution: This line executes immediately and logs “e” to the console.

The Event Loop and Execution Order

  • Main Thread: The code within the Promise executor (logging “x” and “y”) and the console.log("e"); statement execute synchronously.
  • Microtask Queue: Initially empty.
  • Macrotask Queue: Contains the setTimeout callback.
  • Event Loop:

The Event Loop and Execution Order

  • Main Thread: The code within the Promise executor (logging “x” and “y”) and the console.log(“e”); statement execute synchronously.
  • Microtask Queue: Initially empty.
  • Macrotask Queue: Contains the setTimeout callback.
  • Event Loop:
    - The event loop processes the main thread.
    -It checks the microtask queue (empty).
    -It executes the setTimeout callback from the macrotask queue:
    — Logs “setTimeout”.
    — Resolves the Promise, adding the then callback to the microtask queue.
    — Logs “end timeout”.
  • The event loop processes the microtask queue:
    -Executes the then callback, logging “resolved”.

Final Output

The order of output to the console will be:

x
y
e
setTimeout
end timeout
resolved

Key Takeaways

  1. Promise Execution: The code within a Promise’s executor function runs synchronously.
  2. Microtasks vs. Macrotasks: Microtasks (e.g., Promise.then) have higher priority and are executed before macrotasks (e.g., setTimeout).
  3. Event Loop: The event loop orchestrates the execution of code, managing the call stack, microtask queue, and macrotask queue.

Practice Questions

To solidify your understanding, try predicting the output of these scenarios:

Question 1:

const promise = new Promise((resolve) => {
console.log("a");
resolve("b");
});

promise.then((res) => {
console.log(res);
});

console.log("c");

Question 2:

console.log("start");

setTimeout(() => {
console.log("timeout");
}, 0);

Promise.resolve().then(() => {
console.log("promise1");
}).then(() => {
console.log("promise2");
});

console.log("end");

Question 3:

const promise1 = Promise.resolve('p1');
const promise2 = Promise.resolve('p2');

promise1.then((res) => {
console.log(res);
setTimeout(() => {
console.log('timeout1');
}, 0);
});

promise2.then((res) => {
console.log(res);
setTimeout(() => {
console.log('timeout2');
}, 0);
});

console.log('sync');

By working through these examples, you’ll gain a deeper understanding of how JavaScript manages asynchronous operations and the crucial role of Promises and the event loop. This knowledge is invaluable for writing robust and efficient JavaScript applications.

I hope this revised guide is more comprehensive and helpful!

--

--

Ankit Gupta
Ankit Gupta

Written by Ankit Gupta

A Software Developer with extensive experience in building and scaling web and AI-based applications. Passionate about LLMs and AI agents.

Responses (1)