Compile Ready
Module 14 · Machine Coding

Build: Promise Polyfills

Advanced18m read1h practice1h 18m total
Machine CodingPromisePolyfillAsync

Introduction

Promise polyfills are a classic advanced JavaScript machine-coding challenge. The interviewer is not looking for the entire ECMAScript spec; they want the state machine, asynchronous handler scheduling, thenable assimilation, chaining, rejection propagation, and Promise.all behavior.

This topic builds a compact MyPromise with then, catch, resolve, reject, and all.

Why This Matters

Promises are the foundation of modern async JavaScript. Reimplementing them forces you to explain why callbacks run asynchronously, how returned values become chained promises, and how Promise.all preserves order while resolving concurrently.

Theory

Promise state machine

A promise starts as pending. It can transition exactly once to fulfilled with a value or rejected with a reason. After settlement, the state and value never change.

Handler queue

then returns a new promise. If the current promise is pending, store the handler. If it is settled, schedule the handler asynchronously. The returned promise resolves with the callback result; if the callback throws, it rejects.

Thenable assimilation

If resolve receives another promise-like object with a then method, adopt that object's eventual state. This is what makes return fetch(...) or return anotherPromise inside then flatten instead of creating nested promises.

Promise.all

all starts all inputs, stores each result by original index, decrements a remaining counter, resolves when all complete, and rejects immediately on the first rejection.

Visual Diagrams

Promise state transitions
pending
  |
  +-- resolve(value) --> fulfilled(value)
  |
  +-- reject(reason) --> rejected(reason)

settled states are final
then callbacks create a new chained promise

The one-way state transition is the core invariant.

Code Examples

Complete MyPromise and MyPromise.all

This is intentionally compact, but it includes the interview-critical pieces: async scheduling, chaining, thenable adoption, and order-preserving all.

Loading…

Playground

Loading editor…
Console

Press Run to execute the code and see output here.

Output Prediction

Predict the output #1

javascript
1function asyncRun(fn) {
2 if (typeof queueMicrotask === 'function') {
3 queueMicrotask(fn);
4 } else {
5 setTimeout(fn, 0);
6 }
7}
8
9function MyPromise(executor) {
10 this._state = 'pending';
11 this._value = undefined;
12 this._handlers = [];
13 var promise = this;
14 executor(function (value) {
15 settle(promise, 'fulfilled', value);
16 }, function (reason) {
17 settle(promise, 'rejected', reason);
18 });
19}
20
21function settle(promise, state, value) {
22 if (promise._state !== 'pending') {
23 return;
24 }
25
26 promise._state = state;
27 promise._value = value;
28 asyncRun(function () {
29 promise._handlers.forEach(function (handler) {
30 handler(value);
31 });
32 });
33}
34
35MyPromise.prototype.then = function (onFulfilled) {
36 var current = this;
37 return new MyPromise(function (resolve) {
38 function run(value) {
39 resolve(onFulfilled(value));
40 }
41
42 if (current._state === 'pending') {
43 current._handlers.push(run);
44 } else {
45 asyncRun(function () {
46 run(current._value);
47 });
48 }
49 });
50};
51
52MyPromise.resolve = function (value) {
53 return new MyPromise(function (resolve) {
54 resolve(value);
55 });
56};
57
58MyPromise.resolve(2)
59 .then(function (value) {
60 return value + 3;
61 })
62 .then(function (value) {
63 console.log(value);
64 });
65
66console.log('sync');

Coding Exercises

Implement MyPromise with then, catch, and all

Hard

Implement a compact MyPromise.

Requirements:

  • Constructor accepts an executor (resolve, reject).
  • State can move from pending to fulfilled or rejected exactly once.
  • then(onFulfilled, onRejected) returns a new chained promise.
  • catch(onRejected) works as then(null, onRejected).
  • Handlers run asynchronously.
  • Resolving with a thenable adopts that thenable.
  • MyPromise.resolve, MyPromise.reject, and MyPromise.all work.
  • MyPromise.all preserves input order and rejects on first rejection.

Constraints:

  • Do not call native Promise for the state machine.
  • queueMicrotask or setTimeout may be used only for scheduling.

Interview Questions

1Why must promise callbacks run asynchronously?

Asynchronous scheduling makes behavior consistent whether a promise settles before or after then is attached. It also prevents surprising reentrancy where callbacks run in the middle of the current call stack.

Asked at:GoogleMetaMicrosoft

Follow-ups

  • What is the difference between microtasks and macrotasks?
  • Where do promise callbacks sit in the event loop?
2How does `Promise.all` preserve order?

It stores each fulfilled value at the original input index. Promises may resolve in any order, but the final array is ordered by input position, not completion time.

Quiz

1. What should happen if a `then` callback throws?

Summary

  • A promise is a one-way pending to fulfilled/rejected state machine.
  • `then` returns a new promise and schedules callbacks asynchronously.
  • Resolving with a thenable adopts that thenable's eventual state.
  • `Promise.all` preserves input order and rejects on first rejection.

Cheat Sheet

Promise polyfill checklist

  • State: pending, fulfilled, rejected.
  • Store value/reason after settlement.
  • Queue handlers while pending.
  • Schedule handlers asynchronously.
  • then returns a new promise.
  • Callback return value resolves the chained promise.
  • Thrown callback error rejects the chained promise.
  • Assimilate thenables.
  • all: index results, remaining counter, first rejection wins.