JS Guide
HomeQuestionsTopicsCompaniesResources
BookmarksSearch

Built for developers preparing for JavaScript, React & TypeScript interviews.

ResourcesQuestionsSupport
HomeQuestionsSearchProgress
HomeQuestionsjavascript
PrevNext

Learn the concept

Promises & Async/Await

javascript
senior
promise-impl

How would you implement a simplified Promise from scratch?

promise
async
implementation
microtask
chaining
Quick Answer

A Promise implementation requires managing three states (pending/fulfilled/rejected), storing callbacks via then(), executing them asynchronously when the state transitions, and supporting chaining by returning new Promises from then().

Detailed Explanation

Implementing a Promise from scratch tests deep understanding of asynchronous JavaScript, the microtask queue, and callback chaining. A correct implementation must handle state transitions, asynchronous resolution, chaining, and error propagation.

Core Concepts

A Promise has three states:

  • Pending — initial state, neither fulfilled nor rejected
  • Fulfilled — the operation completed successfully with a value
  • Rejected — the operation failed with a reason

Once a Promise transitions from pending to fulfilled or rejected, it cannot change state again. This is a critical invariant.

Implementation Requirements

  1. State management — Track the current state and the resolved value or rejection reason.
  2. Callback storage — then() is called before resolution, so callbacks must be stored and executed later.
  3. Asynchronous execution — Callbacks must execute asynchronously (in a microtask), never synchronously, even if the Promise is already resolved when then() is called.
  4. Chaining — then() returns a new Promise, enabling .then().then().catch() chains.
  5. Error propagation — If a then callback throws, the returned Promise is rejected with that error.
  6. Resolution procedure — If a then callback returns a Promise (thenable), the outer Promise adopts its state.

Promises/A+ Specification

The Promises/A+ spec defines the interoperability standard. Key rules:

  • then must return a Promise
  • If onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure on x
  • If onFulfilled or onRejected throws, the returned Promise must be rejected
  • onFulfilled and onRejected must be called asynchronously (after the execution context stack is empty)

Key Interview Points

  • Why queueMicrotask? Native Promises use microtasks (not macrotasks like setTimeout). queueMicrotask() is the correct scheduling primitive. Using setTimeout works but doesn't match native Promise timing.
  • Thenable resolution — If a callback returns a thenable (object with a .then method), the Promise must recursively resolve it. This is what enables async/await interop.
  • Error handling — Unhandled rejections should propagate. catch() is syntactic sugar for .then(undefined, onRejected).

Code Examples

Simplified Promise ImplementationJavaScript
class MyPromise {
  #state = 'pending';
  #value = undefined;
  #callbacks = [];

  constructor(executor) {
    const resolve = (value) => this.#transition('fulfilled', value);
    const reject = (reason) => this.#transition('rejected', reason);

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  #transition(state, value) {
    if (this.#state !== 'pending') return; // Can only transition once
    this.#state = state;
    this.#value = value;
    this.#executeCallbacks();
  }

  #executeCallbacks() {
    // Execute asynchronously via microtask
    queueMicrotask(() => {
      for (const cb of this.#callbacks) {
        this.#handleCallback(cb);
      }
      this.#callbacks = [];
    });
  }

  #handleCallback({ onFulfilled, onRejected, resolve, reject }) {
    const handler = this.#state === 'fulfilled' ? onFulfilled : onRejected;

    if (typeof handler !== 'function') {
      // Pass through value/reason if no handler
      (this.#state === 'fulfilled' ? resolve : reject)(this.#value);
      return;
    }

    try {
      const result = handler(this.#value);
      // If result is a thenable, adopt its state
      if (result && typeof result.then === 'function') {
        result.then(resolve, reject);
      } else {
        resolve(result);
      }
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      const callback = { onFulfilled, onRejected, resolve, reject };

      if (this.#state === 'pending') {
        this.#callbacks.push(callback);
      } else {
        // Already settled — still execute asynchronously
        this.#callbacks.push(callback);
        this.#executeCallbacks();
      }
    });
  }

  catch(onRejected) {
    return this.then(undefined, onRejected);
  }

  static resolve(value) {
    return new MyPromise((resolve) => resolve(value));
  }

  static reject(reason) {
    return new MyPromise((_, reject) => reject(reason));
  }
}

Real-World Applications

Use Cases

Custom Async Primitives

Understanding Promise internals enables building custom async utilities like cancellable promises, retry logic with exponential backoff, and timeout wrappers.

Framework Development

Libraries and frameworks implement Promise-like abstractions for data fetching, state transitions, and animation sequencing.

Mini Projects

Promise.all and Promise.race

advanced

Extend your custom Promise with static methods all(), race(), allSettled(), and any(). Write tests comparing behavior with native Promise.

Industry Examples

Bluebird

Bluebird was a popular Promise library that added cancellation, timeouts, and performance optimizations before native Promises were fast enough.

Resources

Promises/A+ Specification

docs

MDN - Promise

docs

Related Questions

What are Promises in JavaScript and how do they work?

mid
async

What is the difference between microtasks and macrotasks in JavaScript?

mid
runtime

Explain the JavaScript Event Loop and how it handles asynchronous operations.

mid
runtime
Previous
How would you implement a debounce function from scratch with advanced features?
Next
What is requestIdleCallback and how does it compare to other scheduling APIs?
PrevNext