Custom Promise Implementation0%

Custom Promise Implementation

Beginner12 min read•Updated: Jul 11, 2026
Study Materials

Custom Promise Implementation (Promises/A+ Compliant)

There is no better way to deeply understand asynchronous JavaScript than to build a Promises/A+ compliant Promise class from scratch. This exercise demystifies state transitions, handler queues, and microtask scheduling.


1. The Core Specifications (Promises/A+)

  1. 1
    Three Mutually Exclusive States: PENDING, FULFILLED, REJECTED.
  2. 2
    State Transition Guarantee: PENDING can transition to FULFILLED (with a value) or REJECTED (with a reason). Once settled, state and value are immutable.
  3. 3
    The .then() Method: Returns a new Promise, enabling chaining. Handlers must execute asynchronously via microtasks.

2. Step-by-Step Architecture

Output
┌────────────────────────────────┐
│ MyPromise │
├────────────────────────────────┤
│ state: PENDING │
│ value: undefined │
│ fulfilledCallbacks: [] │
│ rejectedCallbacks: [] │
└───────────────┬────────────────┘
│
┌───────────────┴────────────────┐
│ resolve(val) / reject(err) │
└───────────────┬────────────────┘
│
queueMicrotask()
│
▼
Drain registered callbacks

3. The Implementation

JavaScript
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
 
class MyPromise {
constructor(executor) {
this.state = PENDING;
this.value = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
 
const resolve = (val) => {
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = val;
// Drain callbacks asynchronously in microtask
queueMicrotask(() => {
this.onFulfilledCallbacks.forEach(cb => cb(this.value));
});
}
};
 
const reject = (reason) => {
if (this.state === PENDING) {
this.state = REJECTED;
this.value = reason;
queueMicrotask(() => {
this.onRejectedCallbacks.forEach(cb => cb(this.value));
});
}
};
 
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
 
then(onFulfilled, onRejected) {
// Ensure handlers are functions; fallback to passthrough
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
const realOnRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
 
// .then() MUST return a brand new Promise!
return new MyPromise((resolveNext, rejectNext) => {
const handleCallback = (callback) => {
try {
const result = callback(this.value);
// If returned value is itself a thenable/promise, resolve it
if (result instanceof MyPromise) {
result.then(resolveNext, rejectNext);
} else {
resolveNext(result);
}
} catch (error) {
rejectNext(error);
}
};
 
if (this.state === FULFILLED) {
queueMicrotask(() => handleCallback(realOnFulfilled));
} else if (this.state === REJECTED) {
queueMicrotask(() => handleCallback(realOnRejected));
} else {
// Still PENDING: enqueue handlers
this.onFulfilledCallbacks.push(() => handleCallback(realOnFulfilled));
this.onRejectedCallbacks.push(() => handleCallback(realOnRejected));
}
});
}
 
catch(onRejected) {
return this.then(null, onRejected);
}
 
static resolve(val) {
return new MyPromise((res) => res(val));
}
 
static reject(err) {
return new MyPromise((_, rej) => rej(err));
}
}

4. Testing the Implementation

JavaScript
console.log('1. Start Test');
 
const p = new MyPromise((resolve) => {
setTimeout(() => resolve('Data Loaded!'), 500);
});
 
p.then((data) => {
console.log('2. Step 1 Received:', data);
return 'Next Pipeline Step';
})
.then((next) => {
console.log('3. Step 2 Received:', next);
});
 
console.log('4. End Test');
 
// Logs:
// 1. Start Test
// 4. End Test
// (after 500ms):
// 2. Step 1 Received: Data Loaded!
// 3. Step 2 Received: Next Pipeline Step

Interactive Quiz

Practice Quiz

Question 1
Why must the .then() method return a brand-new Promise instance rather than returning this?
Question 2
Why are registered callbacks executed inside queueMicrotask()?
Question 3
What happens if an executor function throws an uncaught synchronous exception?
Question 4
If onFulfilled is not a function in .then(null, onRejected), what default behavior must be implemented?
Question 5
What should happen if a .then() callback returns another Promise instance?
Next Lesson

Async Generators & For-Await-Of

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext