JavaScript: async and await

async and await are new additions in the ECMAScript 2017 (ES8) specification as a syntactic sugar on top of JavaScript Promises. Their usage eases the way to write and read asynchronous functions besides making their syntax align more with traditional synchronous JavaScript functions.

The async keyword is placed before a function f to make it asynchronous, which then returns a promise.

				
				async function f() {
					//
				}
				
			

We can also denote an async function with an ES6 arrow function notation as

				
				const f = async () => {
					//
				}
				
			

We first consider a simple function pi() which returns the value of π. To wrap it along inside a Promise object, we insert the async keyword as follows:

				
				const pi = async () => {
					return Math.PI;
				}

				pi().then(res => console.log(res));
				
			

Now pi(), despite not being a Promise object initially, returned a Promise (after using async ) and the then() method is chained to it to get the resolved value.

JavaScript await

The await operator delays the async function until the Promise is resolved. Its use, however, is valid only inside the async function's body.

await can only be used inside an async function; outside of it, it will throw a SyntaxError.

Take a look at the async function below (it works without assigning async here), inside of which an instance of the Promise object is invoked without the await expression.

					
					const pi = () => {
						return new Promise((resolve,reject) => {
							setTimeout(() => {
								resolve(Math.PI);
							}, 1000);
						});
					}

					const callPi = async () => {
						pi().then(res => console.log(res));
						console.log("PI");
					}

					callPi();
					
				

The console.log("PI") statement is after the pi().then() asynchronous call. But as the promise is timed to return after 1 second, console.log("PI") gets printed first, followed by the value of π after a second.

					
						PI
						3.141592653589793
					
				

We can do away with the callPi() function replacing it with a self-invoking function as follows:

					
					const pi = () => {
						return new Promise((resolve,reject) => {
							setTimeout(() => {
								resolve(Math.PI);
							}, 1000);
						});
					}

					(async () => {
						pi().then(res => console.log(res));
						console.log("PI");
					})();
					
				

Now if we assign await before the instance of the Promise, it delays the execution of the consecutive console.log("PI") statement till the Promise object is returned.

					
					const pi = () => {
						return new Promise((resolve,reject) => {
							setTimeout(() => {
								resolve(Math.PI);
							}, 1000);
						});
					}

					(async () => {
						await pi().then(res => console.log(res));
						console.log("PI");
					})();
					
				

The console.log("PI"); statement gets executed only after the Promise gets resolved/rejected.

					
						3.141592653589793
						PI
					
				

To handle rejected values in the returned Promise, you need to join the catch() method after then() as shown below.

					
					(async () => {
						await pi()
							.then(res => console.log(res))
								.catch(err => console.log(err));
						console.log("PI");
					})();
					
				

async/await with the fetch() Method

We construct another example with the fetch() method inside.

We will retrieve a list of makeup products of some beauty brand using Heroku's Makeup API, say, the brand Dior.

					
					const makeup = async () => {
					  let response = await fetch('http://makeup-api.herokuapp.com/api/v1/products.json?brand=dior');
					  return await response.json();
					}

					makeup()
					    .then(data => console.log(data))
					    	.catch(err => console.log(err.message));
					
				

The details of each item are printed into the browser's console.

js async fetch response

Replacing Nested & Chained Promises w/ async &await

There often arise a case when the condition to make a subsequent promise call depends upon the result of the first request. For example, take the following scenario:

  • A biker logs in with his email and password into a garage website. A fetch() call is initiated.
  • A promise is returned, which contains his user id. This user id is used to fetch his other details to display in the dashboard, including his last servicing id. This is another fetch() call after the first promise's return.
  • Now the returned servicing id is passed into another fetch() call to retrieve the servicing details.

Already you might have a scanty picture in your mind that this implemention requires nested asynchronous calls. This is one simple example where a subsequent asynchronous call depends upon the data of the preceding request. You cannot make the second call without the result of the first.

We will illustrate it with an example. Using Heroku's Makeup API, we will retrieve all the available makeup items from Dior, Maybelline and Revlon. Though (for the sake of simplicity) we do not involve any dependencies on the datas returned by promises here, the retrieval process goes like this: fetch Dior products first, then Maybelline, then Revlon.

dior maybelline revlon

After the last fetch() call, all the results returned by promises are resolved into a single JavaScript object.

					
					const fetchDetails = (url) => {
					  return fetch(url).then(res => res.json());
					}

					const makeup = () => {
						return new Promise(resolve => {
						  fetchDetails('http://makeup-api.herokuapp.com/api/v1/products.json?brand=dior').then((dior) => {
						  	fetchDetails('http://makeup-api.herokuapp.com/api/v1/products.json?brand=maybelline').then((maybelline) => {
						  		fetchDetails('http://makeup-api.herokuapp.com/api/v1/products.json?brand=revlon').then((revlon) => {
						  			resolve({
									  	'dior': dior,
									  	'maybelline': maybelline,
									  	'revlon': revlon,
									  	'total': dior.length + maybelline.length + revlon.length
									  });
						  		});
						  	})
						  });
						});
					}

					makeup().then(res => {
						console.log(res);
					});
					
				

Here is the output in the browser's console.

js async await nested promise response

As long as we have only such few fetches, nested asynchronous calls look fine. But what if we have to fetch from over a 100 brands? The nesting would extend forever. This is when we enter the "Pyramid of Doom", also known as "Callback Hell". Forget the clutter, the code would be unmanageable!

This is where chaining promises comes in, offering a neater break from deep nesting. We attach the callbacks to the returned promises. The result is the same.

					
					const fetchDetails = (url) => {
					  return fetch(url).then(res => res.json());
					}

					const makeup = () => {
						let dior = maybelline = revlon = [];

						return new Promise(resolve => {
						  fetchDetails('http://makeup-api.herokuapp.com/api/v1/products.json?brand=dior')
						    .then(res => dior = res)
						  	.then(res => fetchDetails('http://makeup-api.herokuapp.com/api/v1/products.json?brand=maybelline'))
						  	.then(res => maybelline = res)
						  	.then(res => fetchDetails('http://makeup-api.herokuapp.com/api/v1/products.json?brand=revlon'))
						  	.then(res => {
						  		revlon = res;
					  			resolve({
								  	'dior': dior,
								  	'maybelline': maybelline,
								  	'revlon': revlon,
								  	'total': dior.length + maybelline.length + revlon.length
								  });
					  		});
					  	});

					}	

					makeup().then(res => {
						console.log(res);
					});
					
				

And better still, we can do away with the nestings and the chainings using async and await. Here is an example showing how they are implemented. The items of Dior are fetched first, then Maybelline's, then Revlon's.

					
					const fetchDetails = (url) => {
					  return fetch(url).then(res => res.json());
					}

					const makeup = async () => {
					  const dior = await fetchDetails('http://makeup-api.herokuapp.com/api/v1/products.json?brand=dior');
					  const maybelline = await fetchDetails('http://makeup-api.herokuapp.com/api/v1/products.json?brand=maybelline');
					  const revlon = await fetchDetails('http://makeup-api.herokuapp.com/api/v1/products.json?brand=revlon');
					  
					  return {
					  	'dior': dior,
					  	'maybelline': maybelline,
					  	'revlon': revlon,
					  	'total': dior.length + maybelline.length + revlon.length
					  };
					}

					makeup().then(res => {
						console.log(res);
					});