/ JavaScript

Promises, Promise Chain and Async/Await ⏳

Promises, Promise Chain and Async/Await ⏳

JavaScript is an even 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 or might not. Which means that there is no guarantee of finishing or in how much time it will take to finish.

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

In earlier time, this asynchronous behaviour was handled with callbacks; callbacks are functions executed by asynchronous functions on various stages of asynchronous execution. Very common examples are DOMContentLoaded event for document or onReadyStateChange function for XMLHttpRequest (was most commonly known as AJAX).

Like as in 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 for a long nested tree of callbacks, it became very hard to manage the code. This problem of long nested callback tree was most commonly referred as the callback hell like in 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 way 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 to create 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, promise's prototype provides who methods named then, catch and finally.

These methods of promise will handle the resolve, reject or settlement cases of promise.

But the interesting thing is that these methods return the promise again with the any return value as a response/resolve.

Which means that the success handler of then function can return promise as well.

So based on the promise result, you can create another promise and return it in 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 simply 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 example for code written above, let's rewrite with async/await.

First thing to remember here is that async is used to create asynchronous function and await is used while calling that function.

I guess if we take the literal word meaning of asynchronous and await and use these words before some action, it will make sense very easily. Like get me some file asynchronously and I will be awaited while you get it.

Lets 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 to note here:

  • These might not work straight out in every browser, so you might wanna 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

Let me know your thoughts and opinions about this article through comments 💬 or on twitter at @patel_pankaj_ and @time2hack.

If you find this article useful, share it with others 🗣