In this note, we will implement the Promise class from scratch.
This walkthrough explains the internal working of JavaScript promises.
Let's start with the core idea.
What is a Promise?
Forget the technical definition for a moment.
Let's understand it with a simple example.
When you order food from platforms like Zomato or Swiggy, after completing the payment process you receive an order summary that says:
Arriving in 20 min
At that moment:
- You don’t have the food yet
- You only have a confirmation that it will arrive in the future
That confirmation behaves like a Promise.
You are free to do other things like:
- Watching movies
- Playing games
- Doing other work
You are not blocked waiting at the door.
Later one of two things happens:
- Food arrives → Success
- Order gets cancelled → Failure
How This Relates to JavaScript
Similarly in JavaScript:
When you make an API call, JavaScript immediately returns a Promise object.
Initially, the promise state is:
PENDING
After the async task finishes:
- Success → Promise becomes
fulfilled
- Failure → Promise becomes
rejected
So a Promise represents a value that will be available in the future.
Implementing Our Own Promise
Now let's build a simplified Promise implementation.
We will call it:
MyPromise
Step 1: Promise States
A promise can exist in three states:
const promiseState = {
PENDING: "pending",
FULFILLED: "fulfilled",
REJECTED: "rejected",
};
Step 2: Promise Class Implementation
const promiseState = {
PENDING: "pending",
FULFILLED: "fulfilled",
REJECTED: "rejected",
};
class MyPromise {
#thenCallbackFunArr = [];
#catchCallbackFunArr = [];
#finallyCallbackFunArr = [];
#state = promiseState.PENDING;
#sucessValue = undefined;
#errorValue = undefined;
constructor(executerFun) {
this.executerFun = executerFun;
this.executerFun(this.#resolver.bind(this), this.#rejecter.bind(this));
}
#resolver(value) {
if (this.#state === promiseState.FULFILLED) return;
this.#sucessValue = value;
this.#state = promiseState.FULFILLED;
// run all then callbacks
this.#thenCallbackFunArr.forEach((cb) => cb(value));
// run finally callbacks
this.#finallyCallbackFunArr.forEach((cb) => cb());
}
#rejecter(error) {
if (this.#state === promiseState.REJECTED) return;
this.#errorValue = error;
this.#state = promiseState.REJECTED;
// run catch callbacks
this.#catchCallbackFunArr.forEach((cb) => cb(error));
// run finally callbacks
this.#finallyCallbackFunArr.forEach((cb) => cb());
}
then(cb) {
if (this.#state === promiseState.FULFILLED) {
cb(this.#sucessValue);
} else {
this.#thenCallbackFunArr.push(cb);
}
return this;
}
catch(cb) {
if (this.#state === promiseState.REJECTED) {
cb(this.#errorValue);
} else {
this.#catchCallbackFunArr.push(cb);
}
return this;
}
finally(cb) {
if (this.#state !== promiseState.PENDING) {
cb();
} else {
this.#finallyCallbackFunArr.push(cb);
}
return this;
}
}
module.exports = MyPromise;
What Is Still Missing
This is a simplified Promise implementation.
Real JavaScript promises support many more features such as:
- Promise chaining
- Microtask queue
- Error propagation
Promise.all
Promise.race
Promise.any
Promise.allSettled
We will implement these in future notes.