Promises, Promise Chain & Async/Await ⏳
Callbacks are old ways for async programming; with Promises, Promise Chains & async/await, you'll have cleaner, manageable & straightforward code.
JavaScript is an event-driven language, and because of that, almost all operations are Asynchronous operations in JavaScript.
But what does it mean? In simple words, we can say the asynchronous operation might finish executing at some unknown time. This means there is no guarantee of finishing or how much time it will take.
TL;DR: Watch the video below to get the gist of this post.
P.S.: This is the first attempt to explain the topic with the help of the video; please excuse for some recording issues
Callbacks
Earlier, this asynchronous behaviour was handled with callbacks; callbacks are functions executed by asynchronous functions on various stages of asynchronous execution. Widespread examples are DOMContentLoaded
event for document
or onReadyStateChange
function for XMLHttpRequest
(was most commonly known as AJAX).
As in the following example:
function sendAjax(callback) {
var xhr = false;
if (window.XMLHttpRequest) {
// code for IE7+, Firefox, Chrome, Opera, Safari
xhr =new XMLHttpRequest();
} else {
// code for IE6, IE5
xhr =new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.open('GET', '/posts', true);
xhr.send();
xhr.onreadystatechange = function() {
if (4 !== xhr.readyState || 200 !== xhr.status) {
// do not precess if request is not complete
return;
}
if (null !== xhr.responseXML) {
// handle XML response
return;
}
if (null === xhr.responseText) {
// handle non-XML response
return;
}
console.log('The AJAX request returned a NULL response');
}
}
var xhr = false;
if (window.XMLHttpRequest) {
// code for IE7+, Firefox, Chrome, Opera, Safari
xhr =new XMLHttpRequest();
} else {
// code for IE6, IE5
xhr =new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.open('GET', '/posts', true);
xhr.send();
xhr.onreadystatechange = function() {
if (4 !== xhr.readyState || 200 !== xhr.status) {
// do not precess if request is not complete
return;
}
if (null !== xhr.responseXML) {
// handle XML response
return;
}
if (null === xhr.responseText) {
// handle non-XML response
return;
}
console.log('The AJAX request returned a NULL response');
}
But the problem with callbacks was that it became tough to manage the code for a long nested tree of callbacks. This problem of long nested callback tree was most commonly referred to as the callback hell like in the following example:
document
.querySelector('#submit')
.addEventListener('click', function() { // #1 callback
// read data from DOM
// send to backend
sendAjax(function(user) { // #2 callback
sendAjax(function(posts) { // #3 callback
sendAjax(function(comments) { // #4 callback
});
});
});
});
Promises
Later promises were introduced, which are more managed ways to execute any asynchronous code.
How they solved the problem of callback hell for asynchronous code execution was by providing a chain-able API to execute callbacks.
Following is the example of a promise-enabled async function fetch
to get some data from some remote URL. The final response is provided to the callbacks via .then
function on fetch
promise.
Following is the example of creating a promise around async code from setTimeout
:
const afterSomeTime = (time) => {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(true);
}, time);
});
return promise;
}
const executeAfterSomeTime = (callback, time) => afterSomeTime(time).then(callback);
executeAfterSomeTime(() => console.log('Hello after 1500ms'), 1500);
Promise Chains
Promise chains are not new but are worth mentioning because of "how promises behave".
The way promises behave is that when you create a promise, the promise's prototype provides who methods named then
, catch
and finally
.
These methods of promise will handle the resolve, rejection or settlement of cases of promise.
But the exciting thing is that these methods return the promise with any return value as a response/resolution.
This means that the success handler of then
function can return a promise as well.
So based on the promised result, you can create another promise and return it to the success handler.
Let’s take a look at an example:
document
.querySelector('#submit')
.addEventListener('click', function() {
// read data from DOM
const name = document.querySelector('#name').value;
// send to backend
fetch(`/users?name=${name}`)
.then(user => {
fetch(`/posts?userId=${user.id}`)
.then(posts => {
fetch(`/comments?post=${posts[0].id}`)
.then(comments => {
//display comments on DOM
});
});
});
});
The above chain can be written as:
document
.querySelector('#submit')
.addEventListener('click', function() {
// read data from DOM
const name = document.querySelector('#name').value;
// send to backend
fetch(`/users?name=${name}`)
.then(user => fetch(`/posts?userId=${user.id}`)))
.then(posts => fetch(`/comments?post=${posts[0].id}`)))
.then(comments => {
//display comments on DOM
});
});
Async/Await
Async/Await is a new way to write cleaner and more understandable code.
So taking the example of the code written above, let's rewrite with async/await.
The first thing to remember here is that async
is used to create an asynchronous function and await
is used while calling that function.
If we take the literal meaning of asynchronous and await and use these words before some action, it will make sense very quickly. Like get me some files asynchronously, and I will be awaited while you get them.
Let's see the example from promise chains:
const getData = async (url) => fetch(url);
document
.querySelector('#submit')
.addEventListener('click', function() {
// read data from DOM
const name = document.querySelector('#name').value;
// send to backend
const user = await fetch(`/users?name=${name}`);
const posts = await fetch(`/posts?userId=${user.id}`);
const comments = await fetch(`/comments?post=${posts[0].id}`);
//display comments on DOM
});
Following this note here:
- These might not work straight out in every browser, so you might want to consider transpiring it
- Notice the position of keywords
async
andawait
await
can not be used withoutasync
; i.e. any function called withawait
has to be a returningpromise
or created withasync
Conclusion
Let me know through comments ? or on Twitter at @heypankaj_ and/or @time2hack
If you find this article helpful, please share it with others ?
Subscribe to the blog to receive new posts right in your inbox.
This post is sponsored by PluralSight