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.

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.

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.

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