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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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();
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();
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
}
)
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); } )
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
}
)
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); } )
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
}
)
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); } )
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);
  }
)
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
})
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); })
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!

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
})
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); })
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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();
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();
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)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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();
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();
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
(async () => {
try {
var response = await fetch(baseURL);
var resp = await response.json();
populatePage(resp)
} catch (err) {
console.info('error', err.message);
}
})()
(async () => { try { var response = await fetch(baseURL); var resp = await response.json(); populatePage(resp) } catch (err) { console.info('error', err.message); } })()
(async () => {
  try {
    var response = await fetch(baseURL);

    var resp = await response.json();

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