Table of contents
    blog cover

    [PART 1] Unlocking JavaScript Interview: Promises and Timers

    Web Development
    Web Development
    Interview
    Interview
    JavaScript interviews often include questions about asynchronous programming, as it's a fundamental concept in modern web development. One common challenge is understanding the intricate dance between Promises, timers, and the event loop. 

    The Challenge

    Let's start with a tricky question that you might encounter in a JavaScript interview. Take a look at the following code and try to determine the order of the console.log outputs:
    // language: javascript
    console.log('1');
    
    setTimeout(() => console.log('2'), 0);
    
    Promise.resolve().then(() => console.log('3'));
    
    process.nextTick(() => console.log('4'));
    
    new Promise((resolve) => {
        console.log('5');
        resolve();
    }).then(() => console.log('6'));
    
    console.log('7');

    Take a moment to think about it. What order do you think the numbers will be logged?


    The Answer and Explanation

    1, 5, 7, 4, 3, 6, 2

    Step-by-Step Analysis:
    1. Synchronous Code Execution:
      • console.log('1'); executes first and prints 1.
      • console.log('5'); inside the new Promise executes next and prints 5.
      • console.log('7'); executes and prints 7.
    2. Asynchronous Code Scheduling:
      • setTimeout(() => console.log('2'), 0); schedules a macrotask.
      • Promise.resolve().then(() => console.log('3')); schedules a microtask.
      • process.nextTick(() => console.log('4')); schedules a microtask. Note: process.nextTick has priority over other microtasks.
      • The new Promise’s .then(() => console.log('6')); schedules a microtask.
    3. Execution of Microtasks:
      • Microtasks are executed after the current synchronous code completes but before any macrotasks.
      • process.nextTick(() => console.log('4')); executes first and prints 4.
      • Promise.resolve().then(() => console.log('3')); executes next and prints 3.
      • new Promise().then(() => console.log('6')); executes last among microtasks and prints 6.
    4. Execution of Macrotasks:
      • The macrotask from setTimeout executes and prints 2.

    Note: If you are seeing 1, 5, 7, 3, 6, 2, 4, it might indicate a difference in the environment where the code is being executed. Typically, process.nextTick should always execute before other microtasks like Promises.

    Event loop. Ref: https://medium.com/@saravanaeswari22/microtasks-and-macro-tasks-in-event-loop-7b408b2949e0


    Understanding Microtasks and Macrotasks in JavaScript

    JavaScript is a single-threaded, non-blocking, asynchronous, concurrent language. This means that while JavaScript executes code on a single thread, it has mechanisms to handle asynchronous operations without blocking the main thread. Key to this functionality is the JavaScript Event Loop, which manages the execution of synchronous and asynchronous code. Within this system, tasks are categorized into two broad types: Microtasks and Macrotasks.

    The JavaScript Event Loop
    Before diving into microtasks and macrotasks, it's essential to understand the event loop. The event loop is responsible for executing the code, collecting and processing events, and executing queued sub-tasks. It continuously checks the call stack to see if there's work to be done.
    When the call stack is empty, the event loop processes tasks from the task queues: the microtask queue and the macrotask (or task) queue.

    Macrotasks
    Macrotasks include events like setTimeout, setInterval, and I/O tasks. These are scheduled in the macrotask queue and executed one at a time. After each macrotask completes, the event loop will check the microtask queue and process all the microtasks before moving on to the next macrotask.
    Examples of Macrotasks:
    • setTimeout
    • setInterval
    • requestAnimationFrame
    • I/O operations

    Microtasks
    Microtasks are tasks that are scheduled to run immediately after the currently executing script, but before any rendering and before any macrotasks. Examples of microtasks include promises (with .then and .catch handlers) and process.nextTick in Node.js. Microtasks are crucial for maintaining a smooth and responsive interface, as they are processed immediately after the current operation completes but before the browser has a chance to render.
    Examples of Microtasks:
    • Promises (.then, .catch, .finally)
    • process.nextTick (Node.js)
    • MutationObserver

    The Order of Execution
    Let’s break down the order of execution in the event loop:
    1. Execute synchronous code: Run all the code that is not inside any callback or asynchronous function.
    2. Process microtasks: After completing the synchronous code, the event loop will process all the tasks in the microtask queue before moving on to the next macrotask.
    3. Process macrotasks: Only after the microtask queue is empty will the event loop move on to the next macrotask.

    Created at 2024-06-23 16:51:19 +0700

    Related blogs