Photo by Karine Avetisyan on Unsplash

Implement your own Promises in JavaScript

Shubham Khatri
Nerd For Tech
Published in
4 min readApr 11, 2021

--

Promises is one of the most fundamental concepts in JavaScript that all of us have used many times in our applications but can we implement our own Promise API? Don’t worry it is not as complicated as it looks. In this post we will implement a basic Promise API ourselves.

What is a Promise?

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

It can be in one of the three states:

  • PENDING, initial state when an operation is in progress
  • FULFILLED, define that the operation was successful
  • REJECTED, denotes a failure in an operation

Note: A promise is said to be settled when it is either fulfilled or rejected. (we are going to use this term a lot in this article)

How do you use a Promise?

For implementing promises, let’s first look at it’s skeleton, essentially the input it takes, and the methods it exposes.

It has a constructor function that takes a callback, and methods like then, catch and finally.

Using a JavaScript Promise

1. Defining the skeleton

We start by defining our Promise class MyPromise.

Following properties are defined in constructor:

  1. state : can be either be PENDING , FULFILLED or REJECTED
  2. handlers: stores callbacks of then, catch, finally methods. (Handlers will only be executed when a promise is settled.)
  3. value : resolve or rejected value.

Note: A promise is executed as soon as it is created, that means our promise callback function will be called inside the constructor with reject and resolve methods passed as parameters to it.

MyPromise skeleton

2. _resolve() and _reject() method implementation

_resolve()or _reject()set the state of promise to FULFILLED or REJECTED respectively, updates the value property and executes the attached handlers.

Note: Nothing happens if we try to call _resolve()or _reject()on an already settled Promise.

Wondering what is isThenable(value) in the above code ?

Well for a case where a Promise is resolved/rejected with another promise, we have to wait for it to complete and then process our current Promise.

isThenable() function implementation

A isThenable function checks if value is an instance of MyPromise or it is an object containing a then function.

3. then() method implementation

then() method takes two arguments as callbacks onSuccess and onFail. onSuccess is called if Promise was fulfilled and onFail is called if Promise was rejected.

“Remember that Promises can be chained”.

The essence of Promise chaining is that the then() method returns a new Promise object. That is how promises can be chained. This is specially useful in scenarios where we need to execute two or more asynchronous operations back to back, where each subsequent operation starts when the previous operation succeeds, with the result from the previous step.

Callbacks passed to then() are stored in handlers array using addHandlers function. A handler is an object {onSuccess, onFail} which will be executed when a promise is settled.

Our implementation of then() looks like this:

then function implementation

4. catch() method implementation

catch() is implemented using then() .We call then() method with the onSuccess callback as null and pass onFail callback as second argument.

5. finally() method implementation

Before we start implementing the finally() method, let us understand its behaviour first (Took me sometime to understand it myself).

From MDN docs:

The finally() method returns a Promise. When the promise is settled, i.e either fulfilled or rejected, the specified callback function is executed. This provides a way for code to be run whether the promise was fulfilled successfully or rejected once the Promise has been dealt with.

The finally() method is very similar to calling .then(onFinally, onFinally) however there are a couple of differences:

When creating a function inline, you can pass it once, instead of being forced to either declare it twice, or create a variable for it

Unlike Promise.resolve(2).then(() => {}, () => {}) (which will be resolved with undefined), Promise.resolve(2).finally(() => {}) will be resolved with 2.

Similarly, unlike Promise.reject(3).then(() => {}, () => {}) (which will be fulfilled with undefined), Promise.reject(3).finally(() => {}) will be rejected with 3.

finally() method returns a Promise which will be settled with previous fulfilled or rejected value.

Summary

We emulated the basic implementation of Promises. There is a lot more to it than then(), catch(), finally() methods which are the instance methods. There are static methods as well which I will try to cover in my future posts.

I hope you enjoyed the article.

Check out the full code on Codepen here.

--

--

Shubham Khatri
Nerd For Tech

Software Engineer at Meta | Passionate about Javascript, React and Web Development | Active Stackoverflow contributor