Introduction
While JavaScript Async/Await have gained immense popularity over the recent years. It is important to understand Promises first, since, they lay the foundation over which Async/Await works.
In this article, we will look at the following concepts:
- What are 'Promises'?
- What are the various execution states of a 'Promise'?
- How to create and modify a 'Promise'?
- How to use the 'Then' & 'Catch' blocks?
- What is 'Promise Chaining'?
What are 'Promises'?
If you've ever been to a Starbucks during peak hours, you would have come across a scenario where the barista asks for your name and notes it down on your cup. When the order is ready, she calls out for you, so that you can pick up your order. Promises do exactly that.
Promises return back the execution status to the calling function to let them know what their current state of execution is.
What are the various execution states of a 'Promise'?
Promises are in one of the below three states:
- Pending - This is the state when the execution is still under processing.
- Resolved - This is the state when the promise is fulfilled and the resolution will be returned back to the calling function.
- Rejected - This is the state when something has gone wrong and the error will be returned back to the calling function.
If you remember the callback example, once the main function passes over the control to the callback function, the responsibility of returning the status of execution to the main function solely lies on the callback function.
'Promises' solve this problem by returning back the status of execution. Let's look at how to create a promise.
How to create & Modify a Promise?
A promise can simply be created by calling the constructor.
const promise = new Promise();
A callback is generally passed to a Promise with the resolve and reject status as parameters as shown below.
//A new promise is created and an anonymous function is passed on resolve & reject as the parameters
const promise = new Promise((resolve, reject) => {
//After a timeout of 3 seconds the status of promise will be changed to resolved
setTimeout(() => {
resolve(); //Promise status changes to resolve
}, 3000) //End of setTimeout method - After 3 sec resolve will be run
})
//Displays the status of the promise immediately. Note that the above function will run after 3 seconds
console.log('Status of Promise before being resolved: ', promise);
//Function will run after 5 seconds and hence will run after the promise has been resolved
setTimeout(() => {
console.log('Status of Promise after being resolved: ', promise);
}, 5000); //Timeout set to 5 seconds
//Output
//Status of Promise before being resolved: Promise {<pending>}
//After 5 seconds, the following will be output to the console
//Status of Promise after being resolved: Promise {<resolved>: undefined}
Things to note:
- A promise is created using the 'new' constructor.
- To the promise constructor, an anonymous function (callback) is passed with the 'resolve' and 'reject' parameters.
- The above example uses ES6 arrow functions and setTimeout for delaying the execution of the function. If you like a refresher on ES6 arrow functions, you can read over here & about setTimeout function over here.
- The anonymous function changes the promise state to resolved after 3 seconds.
- Hence, the first statement will output the status of promise as 'pending'.
- The second anonymous function setTimeout will output the status of promise as 'resolved' since the function runs after 5 seconds and by then the previous anonymous function would have run and changed the status of promise to resolve.
The 'Then' & 'Catch' Blocks
'Then' & 'Catch' are two methods of the JavaScript object that can be invoked. When a promise is resolved, then the function that is passed to the 'then' will be invoked. Likewise, when a promise is rejected, the function passed to the 'catch' will be invoked.
Let us take a look with at the following examples:
Promise (resolved)
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(); //Promise is resolved after 3 seconds
}, 3000)
});
promise.then(onSuccess); //the function 'onSuccess' will be invoked upon resolve()
promise.catch(onError);
function onSuccess() {
console.log('The Promise has been resolved');
} //onSuccess() will be executed since the promise is resolved()
function onError() {
console.log('An error has been encountered');
}
Promise (rejected)
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(); //Promise is rejected after 3 seconds
}, 3000)
});
promise.then(onSuccess);
promise.catch(onError); // the function 'onError' will be invoked on reject()
function onSuccess() {
console.log('The Promise has been resolved');
}
function onError() {
console.log('An error has been encountered');
} //onError() will be executed since the promise is rejected()
A Practical Use-Case of a 'Promise'
Let us modify the previous example to use promise instead of callback.
//Define the Github User ID
const userId = 'skaytech';
/*
Function to fetch data using XMLHTTPRequest
The function uses Promise to resolve, reject based on the external API response
*/
const fetchData = function(userId) {
return new Promise((resolve, reject) => {
//Initialize xhr to a new XMLHttpRequest object
const xhr = new XMLHttpRequest();
// Define the parameters to call an External API
// Calling the Github getUsers API by userId
// Params are - HTTP Method name, URL, Async (true/false)
// When the third param is 'true', it means it's an asynchronous request
xhr.open(
'GET', `https://api.github.com/users/${userId}`, true);
//The onload method will execute when a response has been received from external API
xhr.onload = function() {
//Checking for a response of 200 (It's a success (OK) response)
if (xhr.status === 200) {
//On success - resolve the promise and send response as a parameter
resolve(xhr.responseText);
} else {
//On Error - reject the promise and pass the HTTP status as a parameter
reject(xhr.status);
}
}
//Upon Send the XMLHttpRequest will actual be processed
//This is the method that actually triggers the API call
xhr.send();
});
}
//UI method to display the picture of Github User
function displayUserPicture(response) {
const data = JSON.parse(response);
const imgUrl = data.avatar_url;
document.querySelector('#userimg').setAttribute('src', imgUrl);
}
//UI method to display Error if the Github User does not exits
function onError(status) {
document.querySelector('#userimg').style.display = 'none';
document.querySelector('#errorDiv').textContent = `Error Status: ${status}`;
}
//Invoke the fetch data function & pass the userId as a parameter
//then function is invoked upon success
//catch function will be invoked upon error
fetchData(userId)
.then(response => displayUserPicture(response))
.catch(err => onError(err));
Things that have changed from the previous example:
- The XMLHttpRequest is wrapped within a promise.
- Upon Success, the promise is resolved & the response data is passed as a parameter to the displayUserPicture function.
- Upon Error, the promise is rejected & the error is passed to the onError function.
You can play around with the code over here
Promise Chaining
One last concept, before we are done and dusted with Promises. If you remember, we talked about how asynchronous programming generally isn't naturally tuned to the way we think. Chaining takes care of that and it is easier to explain with the following example.
/*
A function that returns a resolved Promise after 2 seconds
After a duration of 2 seconds, 'Wake up in the morning' is displayed on the console
*/
function getPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Wake up in the morning');
resolve();
}, 2000);
});
}
function workout() {
console.log('Workout');
}
function breakfast() {
console.log('Breakfast');
}
function college() {
console.log('College');
}
function sleep() {
console.log('Sleep');
throw new Error();
}
/*
Promise Chaining in action
Each then resolves and invokes the next function one by one
For e.g. If getPromise() is successful, then workout() is invoked and only if
workout() is successful, then breakfast() is invoked and so on
*/
getPromise()
.then(workout)
.then(breakfast)
.then(college)
.then(sleep)
.catch(err => console.log(err));
//Output
/*
Wake up in the morning
Workout
Breakfast
College
Sleep
Error
at sleep
*/
As you can see, chaining improves readability a lot and it's a lot easier to follow the code and it appears sequentially processing, while it is actually asynchronous in nature.
Conclusion
A quick recap on what we've covered in this article:
- What is a Promise?
- What are the execution states in a promise?
- How to create and modify a promise?
- The 'Then' & 'Catch' blocks of a promise.
- Promise Chaining.
Hope you enjoyed the article. Don't forget to connect with me on Twitter @skaytech