I am making you a pinky promise that by the end of this post you will know JavaScript Promises better.
I have had a kind of “love and hate” relationship with JavaScript. But nevertheless JavaScript was always intriguing for me. Having worked on Java and PHP for the last 10 years, JavaScript seemed very different but intriguing. I did not get to spend enough time on JavaScript and have been trying to make up for it of late.
Promises was the first interesting topic that I came across. Time and again I have heard people saying that Promises saves you from Callback hell. While that might have been a pleasant side-effect, there is more to Promises and here is what I have been able to figure out till now. This is going to be a long article, if you would like highlight some parts you can use our extension http://bit.ly/highlights-extension
Background
When you start working on JavaScript for the first time it can be a little frustrating. You will hear some people say that JavaScript is synchronous programming language while others claim that it is asynchronous. You hear blocking code, non blocking code, event driven design pattern, event life cycle, function stack, event queue, bubbling, polyfill, babel, angular, reactJS, vue JS and a ton of other tools and libraries. Fret not. You are not the first. There is a term for that as well. It is called JavaScript Fatigue. This tweet captures it very well.
If you want further details about JavaScript fatigue you should check out the following article. There is a reason this post got 42k claps on Hackernoon :)
How it feels to learn JavaScript in 2016_No JavaScript frameworks were created during the writing of this article._hackernoon.com
JavaScript is a synchronous programming language. But thanks to callback functions we can make it function like Asynchronous programming language.
Promises for layman
Promises in JavaScript are very similar to the promises you make in real life. So first let us look at promises in real life.
The definition of a promise from the dictionary is as follows
promise : noun : Assurance that one will do something or that a particular thing will happen.
So what happens when somebody makes a promise to you ?
As a rule of thumb, for JavaScript I always read documentation from MDN Web Docs. Of all the resources I think they provide the most concise details. I read up the Promises page form MDSN Web Docs and played around with code to get a hang of it.
There are two parts to understanding promises. Creation of promises and Handling of promises. Though most of our code will generally cater to handling of promises created by other libraries, a complete understanding will help us for sure. Understanding of “creation of promises” is equally important once you cross the beginner stage.
Let us look at the signature for creating a new promise.
new Promise( /* executor */ function(resolve, reject) { ... } );
The constructor accepts a function called executor. This executor
function accepts two parameters resolve
and reject
which are in turn functions. Promises are generally used for easier handling of asynchronous operations or blocking code, examples for which being file operations, API calls, DB calls, IO calls etc.The initiation of these asynchronous operations happens within the executor
function. If the asynchronous operations are successful then the expected result is returned by calling the resolve
function by the creator of the promise. Similarly if there was some unexpected error the reasons is passed on by calling the reject
function.
Now that we know how to create a promise. Let us create a simple promise for our understanding sake.
var keepsHisWord;keepsHisWord = true;promise1 = new Promise(function(resolve, reject) {if (keepsHisWord) {resolve("The man likes to keep his word");} else {reject("The man doesnt want to keep his word");}});console.log(promise1);
Every promise has a state and value
Since this promise gets resolved right away we will not be able to inspect the initial state of the promise. So let us just create a new promise that will take some time to resolve. The easiest way for that is to use the setTimeOut
function.
promise2 = new Promise(function(resolve, reject) {setTimeout(function() {resolve({message: "The man likes to keep his word",code: "aManKeepsHisWord"});}, 10 * 1000);});console.log(promise2);
The above code just creates a promise that will resolve unconditionally after 10 seconds. So we can checkout the state of the promise until it is resolved.
state of promise until it is resolved or rejected
Once the ten seconds are over the promise is resolved. Both PromiseStatus
and PromiseValue
are updated accordingly. As you can see we updated the resolve function so that we can pass a JSON Object instead of a simple string. This is just to show that we can pass other values as well in the resolve
function.
A promise that resolves after 10 seconds with a JSON object as returned value
Now let us look at a promise that will reject. Let us just modify the promise 1 a little for this.
keepsHisWord = false;promise3 = new Promise(function(resolve, reject) {if (keepsHisWord) {resolve("The man likes to keep his word");} else {reject("The man doesn't want to keep his word");}});console.log(promise3);
Since this will create a unhanded rejection chrome browser will show an error. You can ignore it for now. We will get back to that later.
rejections in promises
As we can see PromiseStatus
can have three different values. pending
resolved
or rejected
When promise is created PromiseStatus
will be in the pending
status and will have PromiseValue
as undefined
until the promise is either resolved
or rejected.
When a promise is in resolved
or rejected
states, a promise is said to be settled.
So a promise generally transitions from pending state to settled state.
Now that we know how promises are created we can look at how we can use or handle promises. This will go hand in hand with understanding the Promise
object.
As per MDN documentation
The
**_Promise_**
object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.
Promise
object has static methods and prototype methods
Static methods in Promise
object can be applied independently, whereas the prototype methods
needs to be applied on the instances of Promise
object. Remembering that both normal methods and prototypes all return a Promise
makes it much easier to make sense of things.
Let us first start with the prototype methods
There are three of them. Just to reiterate remember that all these methods can be applied on an instance of Promise
object and all these methods return a promise in turn. All the following methods assigns handlers for different state transitions of a promise. As we saw earlier when a Promise
is created it is in pending
state. One or more of the following three methods will be run when a promise is settled based on whether they are fulfilled
or rejected
.
Promise.prototype.catch(onRejected)
Promise.prototype.then(onFulfilled, onRejected)
Promise.prototype.finally(onFinally)
The below image shows the flow for .then
and .catch
methods. Since they return a Promise
they can be chained again which is also shown in the image. If .finally
is declared for a promise then it will be executed whenever a promise is settled
irrespective of whether it is fulfilled or rejected. As Konstantin Rouda pointed out there is limited support for finally, so please check before you use this.
From : https://mdn.mozillademos.org/files/15911/promises.png
Here is a small story. You are a school going kid and you ask your mom for a phone. She says “I will buy a phone for this month end.”
Let us look at how it will look in JavaScript if the promise gets executed at the end of the month.
var momsPromise = new Promise(function(resolve, reject) {momsSavings = 20000;priceOfPhone = 60000;if (momsSavings > priceOfPhone) {resolve({brand: "iphone",model: "6s"});} else {reject("We donot have enough savings. Let us save some more money.");}});
momsPromise.then(function(value) {console.log("Hurray I got this phone as a gift ", JSON.stringify(value));});
momsPromise.catch(function(reason) {console.log("Mom coudn't buy me the phone because ", reason);});
momsPromise.finally(function() {console.log("Irrespecitve of whether my mom can buy me a phone or not, I still love her");});
The output for this will be.
moms failed promise.
If we change the value of momsSavings
to 200000 then mom will be able to gift the son. In such case the output will be
mom keeps her promise.
Let us wear the hat of somebody who consumes this library. We are mocking the output and nature so that we can look at how to use then and catch effectively.
Since .then
can assign bothonFulfilled, onRejected handlers
, instead of writing separate .then
and .catch
we could have done the same with with .then
It would have looked like below.
momsPromise.then(function(value) {console.log("Hurray I got this phone as a gift ", JSON.stringify(value));},function(reason) {console.log("Mom coudn't buy me the phone because ", reason);});
But for readability of the code I think it is better to keep them separate.
To make sure that we can run all these samples in browsers in general or chrome in specific I am making sure that we do not have external dependencies in our code samples. To better understand the further topics let us create a function that will return a promise which will be resolved or rejected randomly so that we can test out various scenarios. To understand the concept of asynchronous functions let us introduce a random delay also into our function. Since we will need random numbers let us first create a random function that will return a random number between x and y.
function getRandomNumber(start = 1, end = 10) {//works when both start,end are >=1 and end > startreturn parseInt(Math.random() * end) % (end-start+1) + start;}
Let us create a function that will return a promise for us. Let us call for our function promiseTRRARNOSG
which is an alias for promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator
. This function will create a promise which will resolve or reject after a random number of seconds between 2 and 10. To randomise rejection and resolving we will create a random number between 1 and 10. If the random number generated is greater 5 we will resolve the promise, else we will reject it.
function getRandomNumber(start = 1, end = 10) {//works when both start and end are >=1return (parseInt(Math.random() * end) % (end - start + 1)) + start;}
var promiseTRRARNOSG = (promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator
= function() {return new Promise(function(resolve, reject) {let randomNumberOfSeconds = getRandomNumber(2, 10);setTimeout(function() {let randomiseResolving = getRandomNumber(1, 10);if (randomiseResolving > 5) {resolve({randomNumberOfSeconds: randomNumberOfSeconds,randomiseResolving: randomiseResolving});} else {reject({randomNumberOfSeconds: randomNumberOfSeconds,randomiseResolving: randomiseResolving});}}, randomNumberOfSeconds * 1000);});});
var testProimse = promiseTRRARNOSG();testProimse.then(function(value) {console.log("Value when promise is resolved : ", value);});testProimse.catch(function(reason) {console.log("Reason when promise is rejected : ", reason);});
// Let us loop through and create ten different promises using the function to see some variation. Some will be resolved and some will be rejected.
for (i=1; i<=10; i++) {let promise = promiseTRRARNOSG();promise.then(function(value) {console.log("Value when promise is resolved : ", value);});promise.catch(function(reason) {console.log("Reason when promise is rejected : ", reason);});}
Refresh the browser page and run the code in console to see the different outputs for resolve
and reject
scenarios. Going forward we will see how we can create multiple promises and check their outputs without having to do this.
There are four static methods in Promise
object.
The first two are helpers methods or shortcuts. They help you create resolved or rejected promises easily.
Promise.reject(reason)
Helps you create a rejected promise.
var promise3 = Promise.reject("Not interested");promise3.then(function(value){console.log("This will not run as it is a resolved promise. The resolved value is ", value);});promise3.catch(function(reason){console.log("This run as it is a rejected promise. The reason is ", reason);});
Promise.resolve(value)
Helps you create a resolved promise.
var promise4 = Promise.resolve(1);promise4.then(function(value){console.log("This will run as it is a resovled promise. The resolved value is ", value);});promise4.catch(function(reason){console.log("This will not run as it is a resolved promise", reason);});
On a sidenote a promise can have multiple handlers. So you can update the above code to
var promise4 = Promise.resolve(1);promise4.then(function(value){console.log("This will run as it is a resovled promise. The resolved value is ", value);});promise4.then(function(value){console.log("This will also run as multiple handlers can be added. Printing twice the resolved value which is ", value * 2);});promise4.catch(function(reason){console.log("This will not run as it is a resolved promise", reason);});
And the output will look like.
The next two methods helps you process a set of promises. When you are dealing with multiple promises it is better to create an array of promises first and then do the necessary action over the set of promises. For understanding these methods we will not be able to use our handy promiseTRRARNOSG
as it is too random. It is better to have some deterministic promises so that we can understand the behaviour. Let us create two functions. One that will resolve after n seconds and one that will reject after n seconds.
var promiseTRSANSG = (promiseThatResolvesAfterNSecondsGenerator = function(n = 0) {return new Promise(function(resolve, reject) {setTimeout(function() {resolve({resolvedAfterNSeconds: n});}, n * 1000);});});var promiseTRJANSG = (promiseThatRejectsAfterNSecondsGenerator = function(n = 0) {return new Promise(function(resolve, reject) {setTimeout(function() {reject({rejectedAfterNSeconds: n});}, n * 1000);});});
Now let us use these helper functions to understand Promise.All
As per MDN documentation
The
**_Promise.all(iterable)_**
method returns a single[_Promise_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.")
that resolves when all of the promises in the_iterable_
argument have resolved or when the iterable argument contains no promises. It rejects with the reason of the first promise that rejects.
Case 1 : When all the promises are resolved. This is the most frequently used scenario.
console.time("Promise.All");var promisesArray = [];promisesArray.push(promiseTRSANSG(1));promisesArray.push(promiseTRSANSG(4));promisesArray.push(promiseTRSANSG(2));var handleAllPromises = Promise.all(promisesArray);handleAllPromises.then(function(values) {console.timeEnd("Promise.All");console.log("All the promises are resolved", values);});handleAllPromises.catch(function(reason) {console.log("One of the promises failed with the following reason", reason);});
All promises resolved.
There are two important observations we need to make in general from the output.
First : The third promise which takes 2 seconds finishes before the second promise which takes 4 seconds. But as you can see in the output, the order of the promises are maintained in the values.
Second : I added a console timer to find out how long Promise.All
takes. If the promises were executed in sequential it should have taken 1+4+2=7 seconds in total. But from our timer we saw that it only takes 4 seconds. This is a proof that all the promises were executed in parallel.
Case 2 : When there are no promises. I think this is the least frequently used.
console.time("Promise.All");var promisesArray = [];promisesArray.push(1);promisesArray.push(4);promisesArray.push(2);var handleAllPromises = Promise.all(promisesArray);handleAllPromises.then(function(values) {console.timeEnd("Promise.All");console.log("All the promises are resolved", values);});handleAllPromises.catch(function(reason) {console.log("One of the promises failed with the following reason", reason);});
Since there are no promises in the array the returning promise is resolved.
Case 3 : It rejects with the reason of the first promise that rejects.
console.time("Promise.All");var promisesArray = [];promisesArray.push(promiseTRSANSG(1));promisesArray.push(promiseTRSANSG(5));promisesArray.push(promiseTRSANSG(3));promisesArray.push(promiseTRJANSG(2));promisesArray.push(promiseTRSANSG(4));var handleAllPromises = Promise.all(promisesArray);handleAllPromises.then(function(values) {console.timeEnd("Promise.All");console.log("All the promises are resolved", values);});handleAllPromises.catch(function(reason) {console.timeEnd("Promise.All");console.log("One of the promises failed with the following reason ", reason);});
Execution stopped after the first rejection
As per MDN documention
The
**Promise.race(iterable)**
method returns a promise that resolves or rejects as soon as one of the promises in the iterable resolves or rejects, with the value or reason from that promise.
Case 1 : One of the promises resolves first.
console.time("Promise.race");var promisesArray = [];promisesArray.push(promiseTRSANSG(4));promisesArray.push(promiseTRSANSG(3));promisesArray.push(promiseTRSANSG(2));promisesArray.push(promiseTRJANSG(3));promisesArray.push(promiseTRSANSG(4));var promisesRace = Promise.race(promisesArray);promisesRace.then(function(values) {console.timeEnd("Promise.race");console.log("The fasted promise resolved", values);});promisesRace.catch(function(reason) {console.timeEnd("Promise.race");console.log("The fastest promise rejected with the following reason ", reason);});
fastest resolution
All the promises are run in parallel. The third promise resolves in 2 seconds. As soon as this is done the promise returned by Promise.race
is resolved.
Case 2: One of the promises rejects first.
console.time("Promise.race");var promisesArray = [];promisesArray.push(promiseTRSANSG(4));promisesArray.push(promiseTRSANSG(6));promisesArray.push(promiseTRSANSG(5));promisesArray.push(promiseTRJANSG(3));promisesArray.push(promiseTRSANSG(4));var promisesRace = Promise.race(promisesArray);promisesRace.then(function(values) {console.timeEnd("Promise.race");console.log("The fasted promise resolved", values);});promisesRace.catch(function(reason) {console.timeEnd("Promise.race");console.log("The fastest promise rejected with the following reason ", reason);});
fastest rejection
All the promises are run in parallel. The fourth promise rejected in 3 seconds. As soon as this is done the promise returned by Promise.race
is rejected.
I have written all the example methods so that I can test out various scenarios and tests can be run in the browser itself. That is the reason you don’t see any API calls, file operations or database calls in the examples. While all of these are real life example you need additional effort to set them up and test it. Whereas using the delay functions gives you similar scenarios without the burden of additional setup. You can easily play around with the values to see and checkout different scenarios. You can use the combination of promiseTRJANSG
, promiseTRSANSG
and promiseTRRARNOSG
methods to simulate enough scenarios for a thorough understanding of promises. Also use of console.time
methods before and after relevant blocks will help us identify easily if the promises are run parallelly or sequentially . Let me know if you have any other interesting scenarios or if I have missed something. If you want all the code samples in a single place check out this gist.
Bluebird has some interesting features like
We will discuss these in a separate post.
I will also be writing one more post about my learnings from async and await.
Before closing I would like to list down all the thumb rules I follow to keep my head sane around promises.
resolve
maps to then
and reject
maps to catch
for all practical purposes..catch
and .then
methods for all the promises..finally
Promise
object whether they are static methods or prototype methods is again a Promise
Promise.all
the order of the promises are maintained in values variable irrespective of which promise was first resolved.Please point out if I am missing something here or if something can be improved. I started spending more time on Javascript as we are building our current product https://learningpaths.io/ on MERN stack. I had spent a couple of days Googling and reading about promises. It was a mess to keep track of links and notes. So we set out to build a chrome extension that will exactly address this problem and helps you organize your learning paths. It is still in alpha stage. We will be sharing it with a selected 100 people who would like to take it for a spin and provide feedback to us. If you are interested head out to https://learningpaths.io/ and let us know that you are interested in getting the early access. If you think your friends or colleagues would be interested do spread the word.
If you liked this article and would like to read similar articles, don’t forget to clap.
Click and drag to clap more than once. 50 is the limit.
This article is a part of my learning series, which I call Understanding X. You can read more above it in this link.
Understanding Javascript
Understanding promises in JavaScript_I have had a kind of “love and hate” relationship with JavaScript. But nevertheless JavaScript was always intriguing…_hackernoon.com
Understanding async-await in Javascript_Async and Await are extensions of promises. So if you are not clear about the basics of promises please get comfortable…_hackernoon.com
Should I use Promises or Async-Await_I recently read a medium post where the author claimed that using async-await is better than using promises. While this…_hackernoon.com
Understanding npm in Nodejs_I think npm was one of the reasons for quick adoption of nodejs. As of writing this article there are close 7,00,000…_hackernoon.com
Understanding Linux
Linux Survival Guide for Beginners_Survival guide for those who are starting with Linux_hackernoon.com
Understanding Zabbix_Monitor all your server infrastructure_hackernoon.com
Understanding UFW_Ubuntu Firewall for humans_hackernoon.com
Linux Systemctl_Monitoring services and starting them on boot_hackernoon.com
Understanding React
Understanding Functional Components_React is all about keeping your front-end code modular and reusable. Components play a crucial role in making React…_hackernoon.com
Understanding Learning
The Thing about Rabbit Holes_The dictionary meaning of a rabbit hole is as follows._medium.com
Understanding Cryptocurrencies
Why comparing cryptocurrency prices is wrong_Price is an important indicator. But it can also be misleading in many cases._blog.goodaudience.com
Beginner’s Guide to “Investing in Cryptocurrencies”_It has been more than an year since I started investing in cryptocurrencies. Following Warren Buffet’s advice of “Never…_hackernoon.com