JavaScript : Learn Async/Await by Examples

Async/await functions, help us us to write completely synchronous-looking code while performing asynchronous tasks behind the scenes.

It can be Asynchronous. It can be Functional. It can be Object-oriented. It can be Client-side. It can be Server-side. The list goes on. This article will focus on Asynchronous JavaScript.


But wait, JavaScript is a synchronous language!

This means only one operation can be carried out at a time. But that’s not the entire picture here. There are many ways JavaScript provides us with the ability to make it behave like an asynchronous language. One of them is with the Async-Await clause.

What is async-await?

Async and Await are extensions of promises. If you are not clear with the concepts of Promise, you can refer my previous post How to write Promises in JavaScript.

Promises give us an easier way to deal with asynchrony in our code in a sequential manner. Considering that our brains are not designed to deal with asynchronicity efficiently, this is a much welcome addition.

Async/await functions, a new addition with ES2017 (ES8), help us even more in allowing us to write completely synchronous-looking code while performing asynchronous tasks behind the scenes.

The functionality achieved using async functions can be recreated by combining promises with generators, but async functions give us what we need without any extra boilerplate code.
. . .

Simple Example

In the following example, we first declare a function that returns a promise that resolves to a value of 🤡 after 2 seconds. We then declare an async function and await for the promise to resolve before logging the message to the console:
function scaryClown() { return new Promise(resolve => { setTimeout(() => { resolve('🤡'); }, 2000); }); } async function msg() { const msg = await scaryClown(); console.log('Message:', msg); } msg(); // Message: 🤡 <-- after 2 seconds

await is a new operator used to wait for a promise to resolve or reject. It can only be used inside an async function.

The power of async functions becomes more evident when there are multiple steps involved:
function who() { return new Promise(resolve => { setTimeout(() => { resolve('🤡'); }, 200); }); } function what() { return new Promise(resolve => { setTimeout(() => { resolve('lurks'); }, 300); }); } function where() { return new Promise(resolve => { setTimeout(() => { resolve('in the shadows'); }, 500); }); } async function msg() { const a = await who(); const b = await what(); const c = await where(); console.log(`${ a } ${ b } ${ c }`); } msg(); // 🤡 lurks in the shadows <-- after 1 second

A word of caution however, in the above example each step is done sequentially, with each additional step waiting for the step before to resolve or reject before continuing.

If you instead want the steps to happen in parallel, you can simply use Promise.all to wait for all the promises to have fulfilled:
// ... async function msg() { const [a, b, c] = await Promise.all([who(), what(), where()]); console.log(`${ a } ${ b } ${ c }`); } msg(); // 🤡 lurks in the shadows <-- after 500ms

Promise.all returns an array with the resolved values once all the passed-in promises have resolved.

In the above we also make use of some nice array destructuring to make our code succinct.
. . .

Async functions

Let’s start with the async keyword. It can be placed before a function, like this:
async function f() { return 1; }

The word “async” before a function means one simple thing: a function always returns a promise. Other values are wrapped in a resolved promise automatically.
For instance, this function returns a resolved promise with the result of 1, let’s test it:
async function f() { return 1; } f().then(alert); // 1

…We could explicitly return a promise, that would be the same:
async function f() { return Promise.resolve(1); } f().then(alert); // 1

So, async ensures that the function returns a promise, and wraps non-promises in it. Simple enough, right? But not only that. There’s another keyword, await, that works only inside async functions, and it’s pretty cool.
. . .

Await

The syntax:
// works only inside async functions let value = await promise;

The keyword await makes JavaScript wait until that promise settles and returns its result. Here’s an example with a promise that resolves in 1 second:
async function f() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("done!"), 1000) }); let result = await promise; // wait until the promise resolves (*) alert(result); // "done!" } f();

The function execution “pauses” at the line (*) and resumes when the promise settles, with result becoming its result. So the code above shows “done!” in one second.

Let’s emphasize: await literally makes JavaScript wait until the promise settles, and then go on with the result. That doesn’t cost any CPU resources, because the engine can do other jobs meanwhile: execute other scripts, handle events etc.

It’s just a more elegant syntax of getting the promise result than promise.then, easier to read and write.
. . .
Common Mistakes
  • Can’t use await in regular functions
If we try to use await in non-async function, there would be a syntax error:
function f() { let promise = Promise.resolve(1); let result = await promise; // Syntax error }
We will get this error if we do not put async before a function. As said, await only works inside an async function.

  • await won’t work in the top-level code
People who are just starting to use await tend to forget the fact that we can’t use await in top-level code. For example, this will not work:
// syntax error in top-level code let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json();
We can wrap it into an anonymous async function, like this:
(async () => { let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); ... })();

. . .

Error Handling

Something else that’s very nice about async functions is that error handling is also done completely synchronously, using good old try…catch statements. Let’s demonstrate by using a promise that will reject half the time:
function yayOrNay() { return new Promise((resolve, reject) => { const val = Math.round(Math.random() * 1); // 0 or 1, at random val ? resolve('Lucky!!') : reject('Nope 😠'); }); } async function msg() { try { const msg = await yayOrNay(); console.log(msg); } catch(err) { console.log(err); } } msg(); // Lucky!! msg(); // Lucky!! msg(); // Lucky!! msg(); // Nope 😠 msg(); // Lucky!! msg(); // Nope 😠 msg(); // Nope 😠 msg(); // Nope 😠 msg(); // Nope 😠 msg(); // Lucky!!

Given that async functions always return a promise, you can also deal with unhandled errors as you would normally using a catch statement:
async function msg() { const msg = await yayOrNay(); console.log(msg); } msg().catch(x => console.log(x));

This synchronous error handling doesn’t just work when a promise is rejected, but also when there’s an actual runtime or syntax error happening.

In the following example, the second time with call our msg function we pass in a number value that doesn’t have a toUpperCase method in its prototype chain. Our try…catch block catches that error just as well:
function caserUpper(val) { return new Promise((resolve, reject) => { resolve(val.toUpperCase()); }); } async function msg(x) { try { const msg = await caserUpper(x); console.log(msg); } catch(err) { console.log('Ohh no:', err.message); } } msg('Hello'); // HELLO msg(34); // Ohh no: val.toUpperCase is not a function

. . .

Conslusion

The async keyword before a function has two effects:
  1. Makes it always return a promise.
  2. Allows to use await in it.
The await keyword before a promise makes JavaScript wait until that promise settles, and then:
  1. If it’s an error, the exception is generated, same as if throw error were called at that very place.
  2. Otherwise, it returns the result.
Together they provide a great framework to write asynchronous code that is easy both to read and write.

With async/await we rarely need to write promise.then/catch, but we still shouldn’t forget that they are based on promises, because sometimes (e.g. in the outermost scope) we have to use these methods. Also Promise.all is a nice thing to wait for many tasks simultaneously.
Mike John

Mar 14 2019

Write your response...

On a mission to build Next-Gen Community Platform for Developers