Yuan's Blog
EN

Event Loop, Promise, and Async/Await

Explaining the Event Loop in the Browser

  • The Event Loop is not a feature of JavaScript itself. It is a scheduling mechanism provided by the browser or Node.js runtime. JavaScript itself is single-threaded and executes synchronously.
  • The Event Loop consists of the following steps: The main thread executes synchronous code → added to the Stack. When it encounters an asynchronous task (e.g., setTimeout) → the browser hands it off to Web APIs. After the async task finishes → its callback is placed into the task Queue. When the Stack is empty → the Event Loop moves the first task from the Queue into the Stack. Steps 1–4 repeat → this is the Event Loop.
  • The Stack runs synchronous code, the Queue stores asynchronous callbacks, and the Event Loop is responsible for delivering queued callbacks back to the Stack for execution.
  • Order of asynchronous tasks: Within one iteration of the Event Loop, only one macro task is taken and executed. After it finishes, the engine checks the microtask queue, pulling in microtasks continuously until that queue is empty, and then proceeds to the next macro task.
    • Macro Tasks: script (entire program), setTimeout, setInterval, I/O, events, postMessage, MessageChannel, setImmediate (Node.js)
    • Micro Tasks: Promise.then, MutationObserver, process.nextTick (Node.js)
  • Only the first macro task is executed per loop cycle; afterward, the engine checks and drains the microtask queue before moving on to the next macro task.

What Is a Promise and What Is It Used For?

  • A callback function is a function passed as an argument so that another function can invoke it at some point in the future.
  • A Promise is a constructor function, and we create a Promise instance by using the new keyword.
fetch('https://explainthis.com/data')
  .then((response) => response.json())
  .then((data) =>{
    console.log(data);
  })
  .catch((error) =>{
    console.error(error);
  })
  .finally(() =>{
    console.log("close loader");
  });
  • async/await is syntactic sugar built on top of Promises.
async function getData() {
  const res = await fetch("https://getsomedata");
  const data = await res.json();
  console.log(data);
}
getData()

What Is Promise.race? Implement Promise.race

  • Promise.race takes an iterable of multiple promises, such as an Array, Map, or Set. It returns the one that settles first—whether it is fulfilled or rejected.
function promiseRace (promises) {
  for (const p of promises) {
    p.then((val) =>{
      resolve(val);
    }).catch((e) => {
      reject(e);
    })
  }
}

const slow = new Promise(r => setTimeout(() => r("slow"), 2000));
const fast = new Promise(r => setTimeout(() => r("fast"), 500));

promiseRace([slow, fast]).then(console.log);
// → "fast"

What Is Promise.all? Implement Promise.all

  • It takes an iterable (typically an array) and executes all Promises in parallel (it does not wait for them sequentially). Only when all Promises are fulfilled: → it returns a new Promise whose value is an array → and the order of the results matches the input order, not the completion order. If any promise fails: → Promise.all immediately rejects and stops waiting for the remaining tasks.
Promise.all([
  Promise.resolve(1),
  Promise.reject("oops"),
  Promise.resolve(3)
])
  .then(console.log)
  .catch(console.error);   // Output: "oops"

What Are async/await in JavaScript? How Do They Differ From Promises?

  • Syntax: async/await provides a cleaner and more intuitive syntax that makes asynchronous code easier to read and maintain. Promises require using then and catch to handle results and errors, which is more verbose.

  • Error handling: With async/await, you can use try...catch directly for error handling. Promises rely on the catch method.

  • Code flow: async/await makes asynchronous code look and behave more like synchronous code, improving readability. Promise-based code tends to be less linear and sometimes harder to follow.