Implement your own Promises in JavaScript
Tags: javascript, codenewbie, webdev
Published: 2021-07-12 06:18:42


Promises are 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**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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?

Let’s first look at its skeleton for implementing promises, 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.

```javascript
const promise = new Promise((resolve, reject) => {
   /*
     Your code logic goes here and you call  resolve(value)
     or reject(error) to resolve or reject the promise
   */ 
})

promise.then((value) => {
   // Code logic on success of an operation
}).catch(error => {
  // Code logic on failure of an operation
}).finally(() => {
  // Code logic to be executed after completion of operation
})
```

## 1. Defining the skeleton

We start by defining our Promise class **MyPromise**.

### Following properties are defined in the constructor:

1. `state `: can be either be `PENDING`, `FULFILLED` or `REJECTED`

1. `handlers`: stores callbacks of then, catch, finally methods. *(Handlers will only be executed when a promise is settled.)*

1. `value` : resolve or rejected value.

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

```javascript

const STATE = {
  PENDING: 'PENDING',
  FULFILLED: 'FULFILLED',
  REJECTED: 'REJECTED',
}
class MyPromise {
    constructor(callback) {
      // Initial state of Promise is empty
      this.state = STATE.PENDING;
      this.value = undefined;
      this.handlers = [];
      // Invoke callback by passing the _resolve and the _reject function of our class
      try {
        callback(this._resolve, this._reject);
      } catch (err) {
        this._reject(err)
      }
    }
  
    _resolve = (value) => {}
  
    _reject = (error) => {}
  
    then(onSuccess, onFail) {
    }
    
    catch(onFail) {
    }
    
    finally(callback) {
    }
}

```

## 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.

```javascript
  _resolve = (value) => {
    this.updateResult(value, STATE.FULFILLED);
  }
  
  _reject = (error) => {
    this.updateResult(error, STATE.REJECTED);
  }
  
  updateResult(value, state) {
    // This is to make the processing async
    setTimeout(() => {
      /*
        Process the promise if it is still in a pending state. 
        An already rejected or resolved promise is not processed
      */
      if (this.state !== STATE.PENDING) {
        return;
      }
    
      // check is value is also a promise
      if (isThenable(value)) {
        return value.then(this._resolve, this._reject);
      }
      
      this.value = value;
      this.state = state;
      
      // execute handlers if already attached
      this.executeHandlers();
    }, 0);
  }
```

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

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

```javascript
function isThenable(val) {
  return val instanceof MyPromise;
}

// or

function isThenable(value) {
  if (typeof value === "object" && value !== null && value.then && typeof value.then === "function") {
    return true;
  }
  return false;
}
```

## 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 especially 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:

```javascript
then(onSuccess, onFail) {
  return new MyPromise((res, rej) => {
      this.addHandlers({
        onSuccess: function(value) {
          // if no onSuccess provided, resolve the value for the next promise chain
          if (!onSuccess) {
            return res(value);
          }
          try {
            return res(onSuccess(value))
          } catch(err) {
            return rej(err);
          }
        },
        onFail: function(value) {
          // if no onFail provided, reject the value for the next promise chain
          if (!onFail) {
            return rej(value);
          }
          try {
            return res(onFail(value))
          } catch(err) {
            return rej(err);
          }
        }
      });
  });
}

addHandlers(handlers) {
  this.handlers.push(handlers);
  this.executeHandlers();
}

executeHandlers() {
  // Don't execute handlers if promise is not yet fulfilled or rejected
  if (this.state === STATE.PENDING) {
    return null;
  }

  // We have multiple handlers because add them for .finally block too
  this.handlers.forEach((handler) => {
    if (this.state === STATE.FULFILLED) {
      return handler.onSuccess(this.value);
    } 
    return handler.onFail(this.value);
  });
  // After processing all handlers, we reset it to empty.
  this.handlers = [];
}


```

## 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.

```javascript

    /*
        Since then method take the second function as onFail, 
        we can leverage it while implementing catch
    */
    catch(onFail) {
      return this.then(null, onFail);
    }
```

## **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`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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.

```javascript
    // Finally block returns a promise which fails or succeedes with the previous promise resove value
    finally(callback) {
      return new MyPromise((res, rej) => {
         let val;
         let wasRejected;
         this.then((value) => {
           wasRejected = false;
           val = value;
           return callback();
         }, (err) => {
           wasRejected = true;
           val = err;
           return callback();
         }).then(() => {
           // If the callback didn't have any error we resolve/reject the promise based on promise state
           if(!wasRejected) {
             return res(val);
           } 
           return rej(val);
         })
      })
    }
```

Check out the full code implementation in the codepen below:

{% codepen https://codepen.io/mayank_shubham/pen/abpEVJK %}

### 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.

***Thank you for reading...***

If you have any suggestions or questions, please feel free to comment or [DM me on Twitter](https://twitter.com/ShubhamReacts)