← JavaScript core concepts for interviews

Lesson 4 of 5

The event loop, promises and async/await

2 min read

JavaScript runs your code on a single thread. It handles slow operations (network, timers, files) without blocking by handing them to the browser or Node, then running a callback when they finish.

The event loop in four parts

  1. Call stack: the functions currently running.
  2. Web APIs / Node APIs: timers, fetch, file I/O run here, outside your thread.
  3. Task queue (macrotasks): callbacks from setTimeout, setInterval, I/O, UI events.
  4. Microtask queue: promise callbacks (.then, await continuations), queueMicrotask.

The loop: run the current script to completion → run all microtasks → render if needed → take one macrotask → run all microtasks → repeat.

js
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// 1 4 3 2

Synchronous code first (1, 4), then microtasks (3), then the timer (2), even with a 0 ms delay.

Promises

A promise is a placeholder for a value that arrives later. States: pending → fulfilled or rejected (settled once, never changes again).

js
function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

fetch("/api/jobs")
  .then((res) => res.json())
  .then((jobs) => render(jobs))
  .catch((err) => showError(err))   // catches any error above
  .finally(() => hideSpinner());

Combinators

MethodResolves whenRejects when
Promise.allall fulfil (array of results)any one rejects (fail fast)
Promise.allSettledall settle (array of {status, value/reason})never
Promise.racefirst one settles (either way)first one rejects
Promise.anyfirst one fulfilsall reject

async / await

async functions always return a promise. await pauses the function (not the thread) until the promise settles.

js
async function loadDashboard(userId) {
  try {
    // Run independent requests in parallel, not one after another
    const [user, jobs] = await Promise.all([getUser(userId), getJobs(userId)]);
    return { user, jobs };
  } catch (err) {
    console.error(err);
    throw new Error("Could not load dashboard");
  }
}

Common mistakes:

  • await inside a for loop when the calls are independent (slow, sequential). Use Promise.all.
  • arr.forEach(async (x) => await save(x)) does not wait. Use for...of with await, or Promise.all(arr.map(save)).
  • Forgetting try/catch (or .catch) leads to unhandled rejections.

Implement Promise.all (a frequent ask)

js
function promiseAll(items) {
  return new Promise((resolve, reject) => {
    const results = [];
    let done = 0;
    if (items.length === 0) return resolve(results);
    items.forEach((item, i) => {
      Promise.resolve(item).then((value) => {
        results[i] = value;              // keep the original order
        if (++done === items.length) resolve(results);
      }, reject);
    });
  });
}

Check yourself

  • Predict the output of a mix of setTimeout, Promise.then and console.log.
  • Why doesn't await inside forEach work as expected?
  • Difference between Promise.all and Promise.allSettled?

Sign up free to save your progress.