From callback hell, through promises to async functions

Working with asynchronous JavaScript has changed a lot in the last few years. When Promises were introduced to ECMAScript a few years ago life became a dream. Two years later, the async function was added to the specification — I still can’t believe how nice and easy to read it is! What’s next? Time will tell, but now let’s review what we went through to get where we are. You have to agree — a random Chuck Norris joke generator is the perfect demo program!

Callbacks

If you joined the JS stage a few years ago or earlier you’ll remember the doom that you could easily end up in by nesting callbacks. Luckily those days are over. A simple XMLHttpRequest request fetches a piece of JSON from an API and it isn’t hard to read (yet). Imagine a situation where you have to call another request based on the result of the first one — I don’t miss that.

function getRandomJoke() {

  // show loading screen
  loading.classList.add('loading--active');

  var xhr = new XMLHttpRequest();
  xhr.open('GET', 'https://api.icndb.com/jokes/random/1');
  xhr.responseType = 'json';

  xhr.onload = function onload() {
    // print a joke
    main.innerHTML = xhr.response.value[0].joke;

    // hide loading screen
    loading.classList.remove('loading--active');
  };

  xhr.onerror = function onerror() {
    // print an error
    main.textContent = 'Error :-(';

    // hide loading screen
    loading.classList.remove('loading--active');
  };

  xhr.send();
}

See the Pen 2017-10-24-1 by Pawel Grzybek (@pawelgrzybek) on CodePen.

The promise of a better future

An asynchronous operation takes some time and the only thing that we can be assured of ahead of time is the fact that it is going to be resolved or rejected. This could easily be the definition of a JavaScript Promise. It landed in the ECMAScript spec in 2015 and totally revolutionized the way developers handle asynchronous programs. Promised-based methods (the ones that return the promise) are slowly replacing callback-based equivalents and fetch(), used in example below, is one of them.

function getRandomJoke() {

  // show loading screen
  loading.classList.add('loading--active');

  fetch('https://api.icndb.com/jokes/random/1')
    .then(response => response.json())
    .then(joke => {
      // print a joke
      main.innerHTML = joke.value[0].joke;

      // hide loading screen
      loading.classList.remove('loading--active');
    })
    .catch(err => {
      // print an error
      main.textContent = `Error: ${err}`;

      // hide loading screen
      loading.classList.remove('loading--active');
    });
}

See the Pen 2017-10-24-2 by Pawel Grzybek (@pawelgrzybek) on CodePen.

Living on the bleeding edge? Safari 11, Google Chrome 63, EDGE and Node under the -​-harmony-promise-finally flag comes with the additional handler finally(). Keep it DRY!

function getRandomJoke() {

  // show loading screen
  loading.classList.add('loading--active');

  fetch('https://api.icndb.com/jokes/random/1')
    .then(response => response.json())
    .then(joke => {
      // print a joke
      main.innerHTML = joke.value[0].joke;
    })
    .catch(err => {
      // print an error
      main.textContent = `Error: ${err}`;
    })
    .finally(() => {
      // hide loading screen
      loading.classList.remove('loading--active');
    });
}

See the Pen 2017-10-24-3 by Pawel Grzybek (@pawelgrzybek) on CodePen.

Async code, sync look

In 2017 ECMAScript introduced another game changer — the Async Function. It makes working with asynchronous operations very intuitive because it is written in the same way as synchronous code with two tiny nuances — an async keyword in front of a function declaration and an await operator as a promise prefix. Apart from it just being syntactically much nicer to read, it comes with optimizations in JavaScript engines as well. Based on the current browser support and the help that babel-preset-env can offer this is my preferred way of working with asynchronous JavaScript.

const getRandomJoke = async() => {

  // show loading screen
  loading.classList.add('loading--active');

  try {
    const response = await fetch('https://api.icndb.com/jokes/random/1');
    const joke = await response.json();

    // print a joke
    main.innerHTML = joke.value[0].joke;
  }
  catch (err) {
    // print an error
    main.textContent = `Error: ${err}`;
  }
  finally {
    // hide loading screen
    loading.classList.remove('loading--active');
  }
};

See the Pen 2017-10-24-4 by Pawel Grzybek (@pawelgrzybek) on CodePen.

Hopefully you liked this quick time travel through asynchronous programming in JavaScript. The changes introduced in latest versions of the language are really exciting and I am really looking forward to see what’s next! Until next time pals!