How to Create your own Promise
What is a Promise?
A Promise is an object that can represent an asynchronous operation.
This object promises to return the result of this asynchronous operation, be it a success or a failure. If this operation never completes, it will wait till eternity or till it is garbage collected.
What we need is a medium to collect the result of this asynchronous operation from the Promise and that’s where
then() method steps into the limelight.
A thenable is any object that has a
then() method implementation. Yeah, Promises are thenable.
From a Promise context,
then() method is used to add handlers which will eventually receive the result of the asynchronous operation that it represents.
.resolve( 'result' )
.then( successcallback, failurecallback );
The operation when completed leads to the promise getting settled with a resolved
value or a rejected
If the operation completes successfully, the
successcallback( value ) is triggered and the resolved
value is passed as the argument.
If the operation fails magnificently, the
failurecallback( error ) is triggered and the rejected
error is passed as the argument.
A Promise thus can have only three states to represent the asynchronous operation, namely:
- pending: waiting for the asynchronous operation to complete.
- fulfilled: the operation is completed successfully.
- rejected: the operation has failed somehow.
When a Promise gets fulfilled or rejected, it becomes the final state of the Promise, thus making the Promise settled, happily ever after…
Once the promise is settled no further updates can be done to either the result or the state of the promise.
The Promise Constructor:
A Promise constructor creates the promise object for us:
new Promise( executor )
It expects an
executor function which ties the asynchronous activity to the Promise.
executor function is called by the Promise constructor with arguments
reject which are methods provided by the Promise constructor to do the following:
resolve( data ): set Promise state to
fulfilledand the result as
reject( error ): set Promise state to
rejectedand the result as an
Based on the asynchronous activity, the
executor function can then call either
reject method to pass the result to the Promise.
A Promise once constructed has internal properties:
[[PromiseState]]: current state of promise.
[[PromiseResult]]: holds the resolved value or the rejected reason.
Let’s look at an example:
Creating a PromiseCopy:
Let’s start with the
constructor . We receive the
executor and we need to pass
reject methods which are utility functions.
reject are static methods in Promise which are used to create a new Promise as shown below:
When these methods are passed to the
executor function inside the Promise constructor, they behave differently. They are
bind to the Promise instance and returns
With the above knowledge, let’s go ahead and create a PromiseCopy:
Promise.resolve( value ) receives the success response which is set as the
#result of the Promise.
But if this value is a thenable object,
Promise.resolve() doesn’t set this value as the
if you pass a thenable object to
Promise.resolve(), it will keep unwrapping the result by recursively calling
Promise.resolve()until it reaches the final value.
Promise.resolve() receives the final value, it updates the
#result of Promise and sets the state to fulfilled.
Once the Promise is fulfilled, we can dispatch the callbacks waiting eagerly for the
#result. Let’s write the
resolve() method keeping the above things in mind:
reject( reason ) method is pretty similar to the
resolve() method, except it doesn’t do any unwrapping of thenables. It goes straight ahead and sets the reason for the error in the
#result and set the
#state as rejected. Also, yeah dispatch the callbacks:
then() and Promise chaining:
then( successcallback, failurecallback ) is used to add callbacks. These callbacks are stored in the Promise instance and triggered once the Promise is settled.
Promise.then() is also composable meaning that you can chain the
then() calls, and each
then() method receives the result of the previous asynchronous operation.
This is especially useful to avoid callback hell as you can chain dependent asynchronous tasks via Promise chaining.
then( successcallback, failurecallback )always returns a new Promise which gets resolved by the return value of either the
failurecallbackdepending on the
#stateof the current Promise.
failurecallback returns a Promise, the result is unwrapped by the
resolve() method of the new Promise before executing the next
then() method in the Promise chain.
Another important thing is if these callbacks are not passed, the current Promise
#result is forwarded to the new Promise.
Let’s look at the
Please note above that an entry is pushed to
#handlers which will be triggered when the current Promise is settled.
Handlers is an Array that contains callbacks attached using the
then() method. These handlers are kept in reference and triggered asynchronously once the Promise is settled.
Each handler contains methods:
success(): executed when Promise is fulfilled.
fail(): executed when Promise is rejected.
We can create a
#dispatchCallback() method to check if the Promise is settled and eventually call the handlers.
Each handler should be triggered only once after the promise is settled and should be removed from reference afterwards.
As Promise handling is done in the micro-task queue, we can use the
queueMicroTask method to trigger the handlers asynchronously as a microtask.
We also will have a private property
#queued as a check to avoid duplicate triggers:
Log Promise Error
Promise if rejected logs a generic error if a
failurecallback is not provided to handle the error.
Although in promise chaining, a rejected Promise will be forwarded by the
handler till it reaches a Promise with no
#handlers. This essentially can be our check to trigger the error log as done above in
We can attach
failurecallback using the
catch() method as well. However, we also need to forward the value to the next
then() method if the promise is fulfilled.
For this, we can reuse
then() method and also pass
successcallback. Fairly simple right :
finally( finallycallback ) method gets implemented regardless of whether the Promise gets fulfilled or rejected. But it’s not as simple as just passing the
then( finallycallback, finallycallback ) .
The peculiar thing about
finally() is that the current Promise's
#result is not passed to the
#result of the current Promise is passed on to the new Promise returned by
finallycallback is asynchronous, meaning if it returns a Promise or a thenable, the next callback in the Promise chain will only be triggered after this operation is complete.
In a way, the
finallycallback is just some do it anyway intermediate task sitting in between the Promise chaining which has to be successful.
Because if there’s an error in
finallycallback, the new Promise returned by
finally() is rejected with this error.
Let's take a look at the implementation:
You can also check out the below link to play with
The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting…