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.

Promises, Promise Chain & Async/Await ⏳

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 and await
  • await can not be used without async; i.e. any function called with await has to be a returning promise or created with async

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

PluralSight