THN Interview Prep

Asynchronous JavaScript: SDE-3 Reference

This section details JavaScript's asynchronous concurrency model, explaining scheduling mechanics, error boundaries, and state-suspension iterators.


20. Callbacks

A Callback is a function passed as an argument to another function, intended to be executed after a task completes.

Synchronous vs. Asynchronous Callbacks

  • Synchronous Callbacks: Executed immediately on the Call Stack during the caller's execution (e.g. Array.prototype.forEach).
  • Asynchronous Callbacks: Deferred to a future tick of the Event Loop (e.g. setTimeout, network requests).

Inversion of Control & Callback Hell

Asynchronous callbacks yield control flow to third-party code (Inversion of Control). This can lead to security vulnerabilities (e.g. callback executed multiple times when it should run only once) and deep nesting ("Callback Hell"), making error handling complex.

// Error-First Callback Pattern (CommonJS/Node standard)
function readConfig(path, callback) {
  fs.readFile(path, 'utf8', (err, data) => {
    if (err) {
      return callback(err); // Early return with error in first position
    }
    callback(null, JSON.parse(data)); // Success execution
  });
}

21. Promises

A Promise is a proxy object for a value that may not be available yet. It provides structural guarantees for async operations.

Promise States & Lifecycle

Loading diagram…
  • Pending: Initial state; neither fulfilled nor rejected.
  • Fulfilled: Operation completed successfully. The associated value is locked and immutable.
  • Rejected: Operation failed. The associated error is locked.

The Executor Executes Synchronously

The function passed to the Promise constructor (the Executor) runs synchronously when the Promise is instantiated. Only the resolution handlers (.then(), .catch()) are scheduled as asynchronous microtasks.

console.log("1");

new Promise((resolve) => {
  console.log("2 (Executor - Synchronous)");
  resolve("4");
}).then((val) => console.log(`${val} (Promise Handler - Microtask)`));

console.log("3");

// Output: 1, 2, 3, 4

Promise Combinators (Parallel Execution)

CombinatorResolution CriteriaRejection Criteria
Promise.allResolves when all input promises resolve.Rejects immediately when any input promise rejects.
Promise.allSettledResolves when all inputs settle (resolves or rejects), returning an array of status descriptors.Never rejects.
Promise.raceSettles (resolves or rejects) as soon as the first promise settles.Rejects if the first settled promise rejects.
Promise.anyResolves as soon as the first input promise resolves.Rejects with an AggregateError only when all input promises reject.

22. async/await

Introduced in ES2017, async/await is syntactic sugar built on top of Promises and Generators.

Generator-based Transpilation Under the Hood

An async function is transpiled by the engine into a state machine using generators and a promise co-routine handler:

// Syntactic async/await
async function getData() {
  const user = await fetchUser();
  const logs = await fetchLogs(user.id);
  return logs;
}

// Transpiled equivalence using Generators
function getDataTranspiled() {
  return spawn(function* () {
    const user = yield fetchUser();
    const logs = yield fetchLogs(user.id);
    return logs;
  });
}

// Spawn co-routine controller
function spawn(genF) {
  return new Promise((resolve, reject) => {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch (e) {
        return reject(e);
      }
      if (next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(
        (v) => step(() => gen.next(v)),
        (e) => step(() => gen.throw(e))
      );
    }
    step(() => gen.next(undefined));
  });
}

23. Generators & Iterators

JavaScript uses standardized protocols to enable custom iteration flows.

Iteration Protocol & Generators

An Iterator is an object containing a next() method that returns { value: any, done: boolean }. An Iterable is an object containing a [Symbol.iterator] property that returns an Iterator.

Generators (function*) are factory functions that construct Iterators. Calling a generator returns a generator object. When yield is encountered, the function's execution frame is suspended (its stack variables are preserved on the heap), and control returns to the caller.

// Custom Iterator implemented via Generator
const fibonacci = {
  *[Symbol.iterator]() {
    let pre = 0, cur = 1;
    while (true) {
      yield cur;
      [pre, cur] = [cur, pre + cur];
    }
  }
};

// Consuming custom iterable
const iterator = fibonacci[Symbol.iterator]();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

Mark this page when you finish learning it.

Spotted something unclear or wrong on this page?

On this page