JavaScript: Async / Await

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

“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?

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?

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 👀.

Image for post
Image for post

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?

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

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store