Javascript — Custom Promise Polyfill

Eishta Mittal
5 min readOct 30, 2022

Syntax of creating a Promise instance:-

new Promise((resolve, reject)=> { .... })

Here we see that a Promise takes a callback function which further takes two arguments :- resolve method and reject method.

The resolve method is called when the promise is fulfilled and the reject method when error occurs.

  • Starting with the Custom Promise, we need a constructor that takes a callback.
  • This constructor code is wrapped in try…catch as the promise code throws an error whenever it occurs.

The Promise will have:-

  • a value with which it will either resolve or reject.
  • state which can be : pending, fulfilled or rejected.
  • thenCbs array to store all the .then functions on the promise
  • catchCbs array to store all the .catch functions on the promise
  • #onSuccess — private method passed as the first callback in executor function
  • #onFail — private method passed as second callback in executor function — called if any error occurs
  • #onSuccessBinded — bind the onSuccess so that the context is not lost
  • #onFailBinded — binded onFail to avoid context lost

The constructor has its code wrapped in try… catch so that it can handle the error using the onFail method.

When the Promise resolves or rejects

The Promise resolution is handled with the onSuccessBinded function and rejection with the onFailBinded.

Both these methods will take the value with which the promise resolves or rejects.

The #onSucess method does the following here :-

  • Checks if the promise is already in a fulfilled or rejected state and returns. An already settled will not be handled.
  • Sets the value to be passed to the .thenCb.
  • Sets the state to fulfilled to avoid the further resolve and reject to be called in the same promise.
  • Runs the callbacks in .thenCbs array on value with which the Promise resolves.

The promise is already handled in the given case :-

The runcallbacks method looks like :-

It does the following-

  • if the state is fulfilled, then it will run the callbacks in thenCbs array and set the array as empty so that the same methods are not run again on the same promise.
  • it does the same for the rejected promise using the catchCbs array

Prototype Methods

There are only three methods associated with the Promise prototype: —

.then(thenCb, catchCb)

  • called when a promise resolves.
  • It returns a promise to support promise chaining.
  • It takes two arguments — callback functions for the success and failure cases of the Promise.

.catch(catchCb)

  • deals with the rejected cases.
  • It returns a promise to support promise chaining.
  • It internally calls .then(undefined, catchCb)

.finally(cb)

  • It executes the callback cb.
  • It does not effect the state of the origional promise.
  • Unlike Promise.resolve(2).then(() => 77, () => {}) (which will return a resolved promise with the result 77), Promise.resolve(2).finally(() => 77) will return a new resolved promise with the result 2.
  • But, both Promise.reject(3).finally(() => {throw 99}) and Promise.reject(3).finally(() => Promise.reject(99)) will reject the returned promise with the reason 99.

Promise Chaining

To support Promise chaining, then(), catch() and finally() return with a promise on which these methods can further be called(chained).

  • then() now returns a promise for further chaining.
  • it will push all the callbacks
  • if thenCb is not present, resolves with the result of previous promise => passes over the same result.
  • if thenCb is present, resolves with the result of the thenCb called on result => resolve(thenCb(result))

When will thenCb be null?

Check the below case:-

  1. When there is no thenCb

2. The result of the resolved/rejected promise will keep on passing till it finds a then/catch with a handler callback.

A handler returns either a promise or primitive

  • If the value is a primitive, it is coverted to a promise and then passed to the next handler.

Promise and Microtasks

Promise handling is always asynchronous, as all promise actions pass through the internal “promise jobs” queue, also called “microtask queue”.

So to implement the same behaviour, we will use queueMicrotask.

Basically the tasks of queueMicrotask are executed just after current callstack is empty before passing the execution to the event loop.

When no error is handled — Unhandled Promise

  • no .catch() to handle the promise rejection
  • If there’s no rejection handler, the promise throws up its hands and produces a global unhandled rejection error.

How to track it in JS?
The unhandledrejection event is sent to the global scope of a script when a JavaScript Promise that has no rejection handler is rejected.

window.addEventListener("unhandledrejection", (event) => {
console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});

Handling Unhandled Promise in Custom Promise Polyfill

Static methods of Promise

There are 6 static methods of Promise:-

  • Promise.all
  • Promise.allSettled
  • Promise.race
  • Promise.any
  • Promise.resolve
  • Promise.reject

Here we will discuss the Promise.resolve and Promise.reject.

Promise.resolve

Promise.resolve(value) creates a resolved promise with the result value.

Promise.reject

Promise.reject(error) creates a rejected promise with error.

The complete Polyfill looks like:-

Sources

Web Dev Simplified

--

--