JavaScript: Async / Await

Tara Ojo
4 min readJan 7, 2019

I’ll be honest, I was a teeny bit disappointed when async and await became a real thing in JavaScript. Not because they aren’t great, but because I literally just got my head around promises, and was excited to write them more 😂

Never mind. It is by no means wasted learning.

Async / Await are actually like the next level to promises. So, it helps to understand how promises work before using them.

Brief overview of promises

As JavaScript is a synchronous language (it can only handle doing one thing a time), promises allow us to delegate some tasks to somewhere else. Then when it’s done (successfully or not) we can choose to do something with the result.

“The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.”— Mozilla

Let’s look at an example:

function purchaseWine() { 
getWine()
.then(drinkWine)
.catch(reasonForDrinkingWater);
}

Let’s say calls to getWine returns a Promise object. After we get the wine, we want to drink it. Obviously.

The promise returned from getWine allows us to call then() . We give then() a function that will be executed when our promise resolves i.e. when we have some good wine. If something goes wrong though, and our promise rejects, getWine also allows us to call catch() with a function i.e. there isn’t any wine in stock 😢.

Async / Await — what’s the deal?

So the purchaseWine function above works fine, but we can now use async/await to write the same thing. Async/Await allows you to write asynchronous code that looks like synchronous code.

When JavaScript parses your code, it needs to know that something in your function is going to be asynchronous.

To do this we declare the function with the async keyword:

async function purchaseWine () {

Then, you can add await before any call that will return a promise that you want to wait for. We’ll switch the function up to to save our responses into variables too.

async function purchaseWine() { 
const wine = await getWine();
drinkWine(wine);
}

That’s all you need to do, what’s great about this is it looks like pretty generic JavaScript code. You save the result of a function call to a variable then use that variable.

I’ve strategically ignored the catch() from the previous example, but in the async/await world we can use JavaScript’s normal try/ catch to handle any failures.

async function purchaseWine() {
try {
const wine = await getWine();
drinkWine(wine);
} catch (reason) {
reasonForDrinkingWater(reason);
}
}

Though, personally that looks a like more of a faff to me 👀.

So that’s how we use async/await. Though, that doesn’t seem like a big deal. Sure, we’re not chaining promises and passing in callbacks, but is that worth it?

Why should I use it?

Async / await is even more useful when your promises become a little more complex. Specifically when you‘re passing results from one promise into another.

Let’s say we now want to take our wine and use it to find some complementary cheese. This surely must be a thing people do 👀.

In this case we would pass the wine into the getComplementaryCheese function. The problem with this chain of promises is we would lose the original wine from the getWine call by the time we get to the enjoyWineAndCheese call. That’s where promises become a bit of a pain.

function purchaseWineAndCheese() { 
getWine()
.then(getComplementaryCheese)
.then((cheese) => enjoyWineAndCheese(wine, cheese)) // no wine!
.catch(reasonForDrinkingWater);
}

There are a few of ways we can make this work. We could nest one promise inside another, but this can get complicated to read. We could also set the wine value to a variable outside the scope of the promise. This is also not the easiest to read and we end up with extra variables lying around.

The most common approach I’ve seen is to adapt the getComplementaryCheese function to return both the wine and the cheese, so they can then be passed into enjoyWineAndCheese(). This doesn’t really make the most sense as you already have the wine, and you would expect the cheese function to just return cheese.

function getComplementaryCheese(wine) {
const cheese = wine === 'merlot' ? 'gouda' : 'cheddar';
return Promise.resolve({ wine, cheese });
}
function purchaseWineAndCheese() {
getWine()
.then(getComplementaryCheese)
.then(({ wine, cheese }) => enjoyWineAndCheese(wine, cheese))
.catch(reasonForDrinkingWater);
}

But, now we have async/await we can write this in a clearer way…

function getComplementaryCheese(wine) {
const cheese = wine === 'merlot' ? 'gouda' : 'cheddar';
return Promise.resolve(cheese);
}
async function purchaseWineAndCheese() {
try {
const wine = await getWine();
const cheese = await getComplementaryCheese(wine);
enjoyWineAndCheese(wine, cheese);
} catch (reason) {
reasonForDrinkingWater(reason);
}

Now, we have two variables, wine and cheese. We wait for the promises assigned to them both to resolve, then pass them into enjoyWineAndCheese() as they are now accessible like usual variables 🙂. We no longer have to return wine through the getComplementaryCheese() function too!

Cool, right?

--

--

Tara Ojo

Software engineer @ Google. She speaks and writes about career progression and front-end development. @tara_ojo