No One Kept My Promise, So I Built My Own
Rohit Nandi
React.js | React-native | Javascript | Typescript | Node.js | Redux | Jest | Docker
Promises are the bread and butter of modern JavaScript. They help us manage asynchronous code in a clean, structured way. But what if you feel let down by the Promises everyone else keeps? Well, you build your own! In this blog, I’ll walk you through my custom implementation of a Promise class, complete with a sprinkle of fun and plenty of code.
Why Build Your Own Promise?
Before you ask, no, I’m not trying to replace native JavaScript Promises. This was an exercise in learning. By implementing my own Promise, I got to understand the inner workings of JavaScript’s asynchronous capabilities better. Plus, it’s always fun to feel like a JavaScript wizard.
So, let’s dive into how I built my own MyPromise class from scratch.
The Anatomy of MyPromise
Here’s the full implementation of my custom MyPromise class:
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.handlers = [];
try {
executor(this._resolve.bind(this), this._reject.bind(this));
} catch (err) {
this._reject(err);
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.handlers.push({
fulfilled: (value) => {
if (typeof onFulfilled !== 'function') {
resolve(value);
return;
}
try {
resolve(onFulfilled(value));
} catch (err) {
reject(err);
}
},
rejected: (error) => {
if (typeof onRejected !== 'function') {
reject(error);
return;
}
try {
resolve(onRejected(error));
} catch (err) {
reject(err);
}
},
});
this._executeHandlers();
});
}
_executeHandlers() {
if (this.state === 'pending') return;
for (const handler of this.handlers) {
queueMicrotask(() => {
handler[this.state](this.result);
});
}
this.handlers = [];
}
_resolve(value) {
if (this.state !== 'pending') return;
if (value instanceof MyPromise) {
value.then(this._resolve.bind(this), this._reject.bind(this));
return;
}
this.state = 'fulfilled';
this.result = value;
this._executeHandlers();
}
_reject(error) {
if (this.state !== 'pending') return;
this.state = 'rejected';
this.result = error;
this._executeHandlers();
}
catch(onRejected) {
return this.then(undefined, onRejected);
}
static resolve(value) {
return new MyPromise((resolve) => {
resolve(value);
});
}
static reject(value) {
return new MyPromise((resolve, reject) => {
reject(value);
});
}
}
Now let’s break it down piece by piece.
Building Blocks of MyPromise
1. The Constructor
The constructor initializes the promise with a state of pending and an empty list of handlers.
constructor(executor) {
this.state = 'pending';
this.handlers = [];
try {
executor(this._resolve.bind(this), this._reject.bind(this));
} catch (err) {
this._reject(err);
}
}
Here’s what happens:
2. The then Method
The then method registers callbacks for when the promise resolves or rejects.
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.handlers.push({
fulfilled: (value) => {
if (typeof onFulfilled !== 'function') {
resolve(value);
return;
}
try {
resolve(onFulfilled(value));
} catch (err) {
reject(err);
}
},
rejected: (error) => {
if (typeof onRejected !== 'function') {
reject(error);
return;
}
try {
resolve(onRejected(error));
} catch (err) {
reject(err);
}
},
});
this._executeHandlers();
});
}
Key points:
领英推荐
3. The _resolve and _reject Methods
These methods settle the promise by updating the state and value, then executing handlers.
_resolve(value) {
if (this.state !== 'pending') return;
if (value instanceof MyPromise) {
value.then(this._resolve.bind(this), this._reject.bind(this));
return;
}
this.state = 'fulfilled';
this.result = value;
this._executeHandlers();
}
_reject(error) {
if (this.state !== 'pending') return;
this.state = 'rejected';
this.result = error;
this._executeHandlers();
}
4. Static Methods: resolve and reject
These factory methods create settled promises.
static resolve(value) {
return new MyPromise((resolve) => {
resolve(value);
});
}
static reject(value) {
return new MyPromise((resolve, reject) => {
reject(value);
});
}
5. The _executeHandlers Method
This method ensures all registered handlers are executed asynchronously.
_executeHandlers() {
if (this.state === 'pending') return;
for (const handler of this.handlers) {
queueMicrotask(() => {
handler[this.state](this.result);
});
}
this.handlers = [];
}
Testing MyPromise
Let’s see it in action:
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('Hello, MyPromise!'), 1000);
});
promise
.then((value) => {
console.log(value);
return 'Chained value';
})
.then((value) => {
console.log(value);
})
.catch((err) => {
console.error(err);
});
Output:
Hello, MyPromise!
Chained value
Conclusion
Implementing MyPromise was a fantastic way to dive deep into how Promises work. While it’s unlikely you’ll need to replace JavaScript’s native Promises, understanding them at this level gives you a huge advantage when working with asynchronous code.
And hey, now no one can say I didn’t keep my promise!