I always struggle with understanding how async / await works.

So, I decided to write the code down and refer to this rather than trying to think it though every time!

I’ll start with the simple XMLHttpRequest

var req = new XMLHttpRequest();

req.onreadystatechange = function () {

  req.onload = function () {
    if (req.status == 200) {
      console.info('success')
      var arr = JSON.parse(req.responseText);
      populatePage(arr);
    }
    else {
      console.info('error')
    }
  }
}

req.open('GET', baseURL, true);
req.send();

Here, I’m hitting baseURL (defined outside of this code) and doing something with the received array with the populatePage function outside as well.

I wrote it like this so that I don’t add other code which is not relevant to the call.

The main challenge with XMLHttpRequest is that when we need to have nested calls to additional URLs we end up with unreadable code.

Promises were introduced to help solve that.

Promises

Promises essential have a resolve and a reject functions passed in as the first and second parameters.

The first function is executed when the call is successful and the second is executed when the call fails.

They are equivalent to callback functions which are defined as anonymous functions with the then keyword function.

var remoteCall = new Promise(function (resolve, reject) {

  var req = new XMLHttpRequest();

  req.onload = function () {
    if (req.status == 200) {
      resolve(req.responseText);
    }
    else {
      console.info('error')
      reject(req.responseText);
    }
  }

  req.open('GET', baseURL, true);
  req.send();
})

remoteCall.then(
  function (responseText) {
    var arr = JSON.parse(responseText)
    C(responseText, arr)
    populatePage(arr)
  }
  , function (err) {
    console.info('error', err);
    reject(err);
  }
)

Now, replacing XHMHttpRequest with fetch, which is the new way of making remote calls.

var remoteCall = new Promise(function (resolve, reject) {
  var req = fetch(baseURL)
  // console.info('remoteCall promise', req)
  resolve(req);
  reject(req);
})

remoteCall.then(
  function (response) { // * resolve fn
    console.info('remoteCall success')

    var parse = new Promise(function (res, rej) {
      var resp = response.json()
      // console.info('json promise', resp)

      res(resp);
      rej(resp);
    })

    parse
      .then(
        function (r) { // * resolve fn
          console.info('json parse success')
          populatePage(r)
        }
        , function (err) { // * reject fn
          console.info('json parse error', err.message);
        }
      )
  }
  , function (err) { // * reject fn
    console.info('remoteCall error', err.message);
  }
)

Besides the then, we also get a catch when we use promise. Putting that in the above code, we get

Since both the fetch and the response.json() function return promises, we don’t in turn need to wrap them in promises.

var remoteCall = new Promise(function (resolve, reject) {
  var req = fetch(baseURL)
  // console.info('remoteCall promise', req)
  resolve(req);
  reject(req);
})

remoteCall.then(
  function (response) { // * resolve fn
    console.info('remoteCall success')

    var parse = new Promise(function (res, rej) {
      var resp = response.json()
      // console.info('json promise', resp)

      res(resp);
      rej(resp);
    })

    parse
      .then(
        function (r) { // * resolve fn
          console.info('json parse success')
          populatePage(r)
        }
      )
      .catch(
        function (err) { // * reject fn
          console.info('json parse error', err.message);
        }
      )
  }
)
.catch(
  function (err) { // * reject fn
    console.info('remoteCall error', err.message);
  }
)
fetch(baseURL)
  .then(function (response) { // * resolve fn
    console.info('ajax success');

    response.json()
      .then(function (resp) { // * resolve fn
        console.info('json parse success')
        populatePage(resp)
      })
      .catch(function (err) { // * reject fn
        console.info('json parse error', err.message);
      })

  })
  .catch(function (err) { // * reject fn
    console.info('ajax error', err.message);
  })

This can be further simplified by chaining the then functions and having a single catch function.

The chaining of the then functions will work ONLY if the earlier then function does a return!

fetch(baseURL)
  .then(function (response) {
    console.info('ajax success')
    return response.json();
  })
  .then(function (response) {
    console.info('json parse success')
    populatePage(response)
  })
  .catch(function (err) {
    console.info('error', err);
  })

This form of writing the original XMLHttpRequest removes the callback nesting issue as each then gets executed correctly and in the right order.

Async / await goes a bit further in giving a cleaner wrapper around promise and having to struggle with the then and makes code cleaner.

Async / Await

Async / await is basically syntactic sugar on promises.

var getData = async function () {
  try {
    var response = await fetch(baseURL);
    console.info('ajax success');

    var resp = await response.json();
    console.info('json parse success')

    populatePage(resp)
  } catch (err) {
    console.info('error', err.message);
  }
}

getData();

Essentially, anything on the right of await is a promise.

E.g. fetch(baseURL) and response.json() both return promises.

The await keyword ensures that the resolve function is executed and the data returned.

For this to work, all the await statements have to be wrapped in an async function.

Additionally, the reject or the catch functions has been cleaned up by using a simple try-catch block.

Writing the above with arrow functions (ES6)

var getData = async () => {
  try {
    var response = await fetch(baseURL);
    console.info('ajax success');

    var resp = await response.json();
    console.info('json parse success')

    populatePage(resp)
  } catch (err) {
    console.info('error', err.message);
  }
}

getData();

Both these variations read way cleaner than the original XMLHttpRequest and lend to having multiple calls cleanly.

Lastly, we can make the whole thing as an auto-executing function.

(async () => {
  try {
    var response = await fetch(baseURL);

    var resp = await response.json();

    populatePage(resp)
  } catch (err) {
    console.info('error', err.message);
  }
})()