What is ES6 promise in Javascript, when & how to use it
Promise brings the paradigm shift in asynchronous programming. Before promise, we used to write lots of async code with callback and be the victim, sometimes, of callback hell. Callback hell makes our code difficult to read and hard to maintain which makes it easy to introduce bug or error in our code. Thanks to promise, it does not only alleviate such problem but encourage us to write more async code.
To use promise, before it could come natively in Javascript, we used to be dependent on a various external library like Q, When, Bluebird etc
. Each of these promise libraries had a different implementation with a slight deviation from standard behavior. But now, promise has lately(es6[ES2015]) but natively arrived in javascript with standard behavior. In this article, we will learn about the promise which is part of standard Javascript.
As of today, Promise is supported in all the major browser except Internet Explorer.
Conceptualization: example
Remember: we should not just know how we are implementing things; we should also know, what things! we are implementing. So, it is very important to first understand, what is promise, before learning how to implement this.
Promise in Javascript is very much like the promise in the real world. So we will take a real world example and will try to characterize it. Do you know, what do we mean by characterization? Well! every object in the real world has two characteristics: 1. Behavior and 2. State. So here, we will try to find the behavior and state of real world promise.
Example: "Suppose, you made a promise to me that you will go to the market and bring a pen for me." Now let's characterize your promise as shown below.
Behavior: going to market and bringing the pen for me.
State:
- Fulfilled: if you returned from market and brought the pen for me.
- Broken: if you returned from the market but did not bring the pen for me.
- Pending: you have not come yet from market.
It's same three states of promise that exist in Javascript also, but you might hear a different name for the same state. For example, we also call ‘resolved’ for ‘fulfilled’ and ‘rejected’ for ‘broken’.
Sometimes, you will also read/hear about the 4th state of promise, called ‘settled’. ‘settled’ is a state when the promise has either resolved or rejected. So technically, it's not a separate state, instead it is one of ‘resolved’ or ‘rejected’.
Implementation: syntax
So in above section, we have conceptualize the behavior and state of promise. In this section, we will learn how to implement these behavior & state of promise in Javscript. While doing this, we also learn some basic promise syntax in Javascript.
How to create?
In Javascript, promise is actually an object, so if you know how to create object in Javscript then you would easly know how to create promise. So 1st step to create promise object is to use new
operator with its function constructor Promise()
like new Promise()
. See below example 'Create: Step1'.
So now the 2nd step: from our example in the introduction section, we know that promise is a behavior with one of the states from 'resolve', 'reject', or 'pending'. And behavior is nothing but function, work, or operation. So let's define the behavior of our promise. See how to do this in below example 'Create: Step2'.
Notice how we are passing two arguments, namely 'resolve' and 'reject', in function definition of myPromiseBehavior
. We can give any other name to these argument, but we generally keep these name to align with promise concept.
var myPromise = new Promise()
var myPromiseBehavior = function(resolve, reject) {
//do some work here
var data = getSomeData()
if (data) {
//resolve your promise when you think its success
resolve(data)
} else {
//reject your promise when you think its error
reject("could not get the data")
}
}
var myPromise = new Promise(myPromiseBehavior)
See also, how we are deciding the state of our promise? When we think that function has succeeded in its work or purpose then we put that promise in a resolved state by calling a resolve()
function. Similarly, if we think that promise has failed in its work or purpose then we put that promise in a rejected state by calling a reject()
function. If we don't call either resolve
or reject
function, then this promise will always be in pending state.
So promise in Javascript is an object with such a behavior whose state will always be in one of resolved, reject, or pending state.
How to use?
So now we know how to create promise but how do we use it? What do we mean by use? Let's take an example in the real world. Suppose you have made me a promise, now if you break promise: I will cry; if you fulfill the promise: I will smile. So here, depending on your promise state(break or fulfill), I want to do some work(cry or smile). If I don’t cry or smile then there is no use of your promise.
Similarly in Javascript, using promise means, calling some function once the promise is settled. Let's see how to do this in below example, but before this, let us clarify our requirement. The requirement is simple, we want to call function onSuccess
if the promise is resolved and onError
if the promise is rejected.
//To use promise, just call its 'then' method
myPromise.then(onSuccess, onError)
//OR
myPromise.then(onSuccess).catch(onError)
function onSucces() {
console.log("promise has fulfilled")
}
function onError() {
console.log("promise has rejected")
}
So from above code, it seems that there are two ways to use promise. Yes! especially for handling reject(error) state. Promise object has two method then
and catch
. These methods are building blocks for usage of promse. then
method takes two arguments as a functtion. 1st arugment is handler function for resolved state and 2nd argument is handler function for reject state. If we don't pass 2nd argument in then()
method then it call catch
method, catch
method takes one argument which is handler function for reject state.
So to use promise, we utilize its 'then' & 'catch' method. 'then' method takes two handler function as arguments for 'resolve' & 'reject' state respectively. 'cath' method is used to pass reject handler.
Promisify: actual promise's use
Whatever we learned in above section is just the basic concept and syntax of promise. We didn't learned why & where exactly we use promise?
When we call any function, we are mostly interested in whether that function failed or succeeded in its purpose. Depending on its 'failed' or 'succedded' status, we call some other function. Promise also has two states resolved and rejected which is very similar to function's 'failed' or 'succeeded' status. We can apply this promise's state to function's behavior.
To do this, we simply have to return a promise object from function and wrap all function's logic inside that promise object. Making any function to return promise is called Promisifying the function. Any function in Javascript can be promisified but we should only promisify asynchronous function because that is where the power of promise lies.
How to promisify function?
By wrapping all the function logic inside promise constructor. See the example below, but before that, let us clarify our requirement. The requirement is simple: we have a function called getMyFavouriteDigit
, which return my favourite digit(6) asynchronously and we want to promisify this function.
function getMyFavouriteDigit() {
var promiseBehavior = function(resolve, reject) {
//wrap your all function code here
//if success, call resolve with success data
//if error, call reject with error data
}
return new Promise(promiseBehavior)
}
function getMyFavouriteDigit() {
var promiseBehavior = function(resolve, reject) {
setTimeout(() => {
var randomDigit = Math.floor(Math.random() * 9)
if (randomDigit === 6) {
resolve(randomDigit)
} else {
reject(randomDigit)
}
}, 1000)
}
return new Promise(promiseBehavior)
}
See above, how function's logic is wrapped inside promise constructor and how the function is returning promise object.
How to use promisified function?
Since promisified function return promise object, we can use it like a normal promise. Using promise means calling some other dependent function once the promise has settled. For example, we want to call function printMyFavouriteDigit
after getMyFavouriteDigit
function has resolved its promise. See how to do this in below code. See, how we are utilizing promise's then
method.
function printMyFavouriteDigit(digit) {
console.log("I got my favourite digit " + digit)
}
getMyFavouriteDigit.then(printMyFavouriteDigit)
Chaining promises
Promise has some very interesting usage pattern: one of them is called 'chaining of Promises'. We have to take help of Promise's chain, when we come into a situation, where we have multiple interdependent async functions and each of these function should run one after another.
Let's take an example: suppose function funcGetText2
is dependent on the result(text1
) of function funcGetText1
. Similarly funcGetText3
is dependent on funcGetText2
and printFinalText
is dependent on funcGetText3
and of course each function is promisified function. Basically, each function should be called only when its previous function has finished.
See below code on how to use a chain of promise for such scenario, you can also see how to implement the same using callback to just have an idea. Notice, how we are making a chain of then
method by passing each function in required order.
funcGetText1(function(text1) {
funcGetText2(text1, function(text2) {
funcGetText3(text2, function(text3) {
printFinalText(text3)
})
})
})
funcGetText1()
.then(funcGetText2)
.then(funcGetText3)
.then(printFinalText)
Array of promises
Another interesting usage pattern of promise is the 'array of promises'. we use this pattern when we have a situation, where we have multiple independent async functions and all these functions have to run parallelly.
Let's take an example: suppose we want to fetch four URLs at the same time. Fetching of one URL is not dependent on another url, so it has to be done parallelly. We also want to print the result of each URL fetch only when all of the URLs have been fetched. See below code on how to do this.
Assume 'fetch' function in below code returns promise. So first we are creating an array of promise using promiseArray.push()
then we are passing this array to all()
method of promise object. Promise.all()
ensure that any handler in then
or catch
method is called only when all the promise in promisArray
is settled.
var promiseArray = []
var urls = ["url1", "url2", "url3", "url4"]
for (var i = 0; i < urls.length; i++) {
promiseArray.push(fetch(urls[i]))
}
Promise.all(promisArray)
.then(printResult)
.catch(errorHandler)
function printResult(result) {
console.log("all urls has been fetched")
for (var i = 0; i < result.length; i++) {
console.log("result for url ", i, " is ", result[i])
}
}
function errorHandler(err) {
console.log("some error occured while fetching urls: ", err)
}
Please note the result
variable in above code. Variable result
is an array which contains the content of each URL fetch. Number of element in result
will always be equal to a number of element in promiseArray
. Also to be noted is that when any of these promises is rejected then whole Promise.all()
is rejected, resulting in an error condition. When Promise.all()
is rejected, it directly goes to catch()
method, skipping then()
method. In such situation, we should check which all promise is rejected and why in errorHandler
.
Error handling
Whenever promise is rejected, it is treated as an error and we must provide error handler otherwise it will crash the application. There are two way to handle promise error. one way is to use then
method and another way is to catch
method. As discussed in intro section, we pass our error handler as second argument in then
method.
Let's take an example: suppose function getMyFavouriteDigit
is rejecting its promise when it is not able to return my favourite digit 6. See how to handle this in below example.
function getMyFavouriteDigit() {
var promiseBehavior = function(resolve, reject) {
setTimeout(() => {
var randomDigit = Math.floor(Math.random() * 9)
if (randomDigit === 6) {
resolve(randomDigit)
} else {
reject(randomDigit)
}
}, 1000)
}
return new Promise(promiseBehavior)
}
function printMyFavouriteDigit(digit) {
console.log("I got my favourite digit " + digit)
}
function errorHandler(digit) {
console.log("I could not get my favourite digit 6 but " + digit)
}
getMyFavouriteDigit.then(printMyFavouriteDigit, errorHandler)
getMyFavouriteDigit.then(printMyFavouriteDigit).catch(errorHandler)
Now let's take another short example to understand the flow of error handling in a chain of promise. In below code, errorHandler1
will be called when any of funcGetText1
, funcGetText2
and funcGetText3
reject its promise. While errorHandler2
will be called when any of errorHandler1
, funcGetText4
and printFinalText
reject its promise.
funcGetText1()
.then(funcGetText2)
.then(funcGetText3)
.catch(errorHandler1)
.then(funcGetText4)
.then(printFinalText)
.catch(errorHanlder2)
So error handling in Promise's chain is just like try/catch
. Any error in Promise's chain goes to its immediate catch
method in promise chain.
vs callback
As we know, in javascript, we can do async stuff using promise or/and callback. So is there any advantage of one over other? Well! that is what we will see in this section with the help of small example.
Seperation of Concern: Ideally, when we call any function, we should only care about its result and depending on its result, call any other dependent function. But with the callback, it is not possible. In the callback, caller function need to know about dependent function in advance and caller function decide when to call the dependent function.
For example: in below code 'Async: Using Callback', getMyFavouriteDigit
function need to know about printMyFavouriteDigit
function in advance and function 'getMyFavouriteDigit' decide when to call function 'printMyFavouriteDigit'.
To maintain separation of concern: one function needs to know about its own business, not other’s; otherwise, there will be a conflict of interest. Thanks to promise, now each function has their own concern only, dependency is linked at the time or wiring.
function getMyFavouriteDigit(fnCallback) {
setTimeout(()=>{
var randomDigit = Math.floor(Math.random() \* (9 - 1)) + 1;
if(randomDigit === 6){
//see here, need to know about dependent function
fnCallback(randomDigit)
}
}, 2000);
}
function printMyFavouriteDigit(digit){
console.log('I got my favourite digit ' + digit );
}
//See here, need to pass dependent function as argument.
getMyFavouriteDigit(printMyFavouriteDigit);
//no need to know about dependent function
function getMyFavouriteDigit() {
return new Promise((fulfill, reject) => {
setTimeout(() => {
var randomDigit = Math.floor(Math.random() * (9 - 1)) + 1
if (randomDigit === 6) {
fulfill(randomDigit)
}
}, 2000)
})
}
function printMyFavouriteDigit(digit) {
console.log("I got my favourite digit " + digit)
}
//no need to pass dependent function as argument
getMyFavouriteDigit().then(printMyFavouriteDigit)
Callback Hell: with too much usage of callback, we come into a siutation called callback hell that make our code hard to read and difficult to maintain. Thanks to promise, we can avoid this situation.
On the day of writing this article, there is one new alternative called async await which is better than callback and promise.