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); } })()