Step‑by‑Step Guide to Implementing a Custom Promise in JavaScript
This article provides a comprehensive, step‑by‑step tutorial on hand‑coding a JavaScript Promise class, covering its initial structure, state management, then method, error handling, asynchronous execution, callback queuing, and chainable behavior with complete code examples.
Introduction
In this tutorial we will build a hand‑written Promise implementation from scratch. The guide is suitable for readers who are unfamiliar with native JavaScript promises or who want to understand the internal mechanics in depth.
1. Initial Structure – Creating the Class
We start by defining a MyPromise class and creating an instance with new Promise((resolve, reject) => {}) . The constructor receives an executor function that will be called immediately.
let promise = new Promise((resolve, reject) => {});Inside the class we store the executor and later replace the native Promise with our own class:
class MyPromise {
constructor(executor) {
const resolve = (value) => {};
const reject = (reason) => {};
executor(resolve, reject);
}
}2. Defining State and Core Methods
A promise has three possible states: pending , fulfilled , and rejected . We define them as static properties and add instance fields for the current state, the resolved value, and the rejection reason.
class MyPromise {
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor(executor) {
this.state = MyPromise.PENDING;
this.value = undefined;
this.reason = undefined;
const resolve = (value) => {
if (this.state === MyPromise.PENDING) {
this.state = MyPromise.FULFILLED;
this.value = value;
}
};
const reject = (reason) => {
if (this.state === MyPromise.PENDING) {
this.state = MyPromise.REJECTED;
this.reason = reason;
}
};
executor(resolve, reject);
}
}3. Implementing then
The then method receives two callbacks – one for fulfillment and one for rejection. If the promise is already settled, the appropriate callback is executed asynchronously via setTimeout . If the promise is still pending, the callbacks are stored for later execution.
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r };
if (this.state === MyPromise.FULFILLED) {
setTimeout(() => onFulfilled(this.value));
}
if (this.state === MyPromise.REJECTED) {
setTimeout(() => onRejected(this.reason));
}
if (this.state === MyPromise.PENDING) {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
}4. Error Handling
To mimic native behavior, the constructor wraps the executor call in a try…catch block. If the executor throws, reject is called with the error.
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}5. Asynchronous Execution
Native promises resolve in a micro‑task queue. For simplicity we simulate asynchrony with setTimeout inside then and when invoking stored callbacks.
6. Callback Queuing
When the promise is still pending, callbacks are pushed into onFulfilledCallbacks and onRejectedCallbacks . Once resolve or reject runs, each stored callback is called in order.
this.onFulfilledCallbacks.forEach(cb => cb(this.value));
this.onRejectedCallbacks.forEach(cb => cb(this.reason));7. Chainable then
To support chaining, then returns a new MyPromise . The result of the callback (or any thrown error) determines how the new promise settles.
return new MyPromise((resolve, reject) => {
if (this.state === MyPromise.FULFILLED) {
setTimeout(() => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (e) {
reject(e);
}
});
}
// similar handling for REJECTED and PENDING cases
});Conclusion
The final implementation combines static state constants, proper error handling, asynchronous callback execution, callback storage for pending promises, and a chainable then method. With this custom MyPromise you can replicate the core behavior of native JavaScript promises.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.