JavaScript's Async + Await in 5 Minutes

Dec 15, 2019
Javascript
~5min
Share on Twitter

Bye Bye Promise inception and callback fury! ๐Ÿ‘‹๐ŸŽ‰

Itโ€™s likely that youโ€™ve encountered Promises in your JavaScript (If you havenโ€™t check out this guide quick ๐Ÿ‘). They allow you to hook into the completion of asynchronous calls. They make it simple to chain asynchronous operations or even group them together. There is one tiny downside. When consuming Promises, the syntax isnโ€™t always the prettiest.

Introducing async + await ๐ŸŽ‰

For those in camp TL;DR async + await are syntactic sugar for consuming your Promises ๐Ÿญ They aid in understanding the flow of your code. There are no new concepts, itโ€™s Promises with nicer shoes ๐Ÿ‘Ÿ Scroll down for a gist โŒจ๏ธ

Baking a cake with code ๐Ÿฐ permalink

We are going to bake a cake ๐Ÿฐ yum! To bake the cake, we first need to get the ingredients. Iโ€™m sorry, itโ€™s a plain sponge ๐Ÿ˜…

  • Butter
  • Flour
  • Sugar
  • Eggs ๐Ÿฅš

In our code, getting each ingredient requires an asynchronous operation.

For example, here is the method getButter:


            javascript
            
          const getButter = () => new Promise((resolve, reject) => {
  setTimeout(() => resolve('Butter'), 3000)
})

These operations will become part of a getIngredients method. When we bake the cake, we will need to invoke getIngredients before mixing, etc.

With Promises permalink

Letโ€™s assume we need to chain each asynchronous operation. getIngredients is a journey around a supermarket picking up one ingredient at a time ๐Ÿ›’

In most cases, we only need to chain operations if they are dependent on each other. For example, if the second operation needs the return value from the first operation and so on.

In our example, it may be that we can only add one item to our shopping basket at a time. That means we need to progress through the ingredients one by one. Remember the code here is hypothetical and to show the use of Promises ๐Ÿ˜‰

How might getIngredients look with Promises? Iโ€™ve certainly seen nested Promises like this before ๐Ÿ‘€


            javascript
            
          const getIngredients = () => new Promise((resolve, reject) => {
  getButter().then((butter) => {
    updateBasket(butter)
    getFlour().then((flour) => {
      updateBasket(flour)
      getSugar().then((sugar) => {
        updateBasket(sugar)
        getEggs().then((eggs) => {
          updateBasket(eggs)
          resolve(basket)
        })
      })
    })
  })
})

This works but doesnโ€™t look great ๐Ÿ‘Ž It would look better with a Promise chain.


            javascript
            
          const getIngredients = () => getButter()
  .then(updateBasket)
  .then(getFlour)
  .then(updateBasket)
  .then(getSugar)
  .then(updateBasket)
  .then(getEggs)
  .then(updateBasket)

If we were doing our grocery shopping online, we could use Promise.all ๐Ÿค“


            javascript
            
          const getIngredients = () => Promise.all([
  getButter(),
  getFlour(),
  getSugar(),
  getEggs(),
])

These look much tidier but we still need to use a callback to get those ingredients.


            javascript
            
          getIngredients().then(ingredients => doSomethingWithIngredients(ingredients))

Tidying it up with async + await permalink

Letโ€™s sprinkle on that syntactic sugar ๐Ÿญ To use the await keyword, we must first declare a method as asynchronous with the async keyword. Itโ€™s important to note that an async method will always return a Promise. That means there is no need to return a Promise ๐ŸŽ‰

Letโ€™s declare getIngredients as async


            javascript
            
          const getIngredients = async () => {}

Now, how might those Promises look with sugar? The await keyword allows us to wait for a Promise and define a variable with the return value of that Promise. It's a little verbose for this example, but letโ€™s apply that sugar to getIngredients.


            javascript
            
          const getIngredients = async () => {
  const butter = await getButter()
  const flour = await getFlour()
  const sugar = await getSugar()
  const eggs = await getEggs()
  return [
    butter,
    flour,
    sugar,
    eggs,
  ]
}

The code isn't smaller, but it's more verbose and concise ๐Ÿ‘ No more callbacks. It's when we consume a Promise that the syntactic sugar comes into play.


            javascript
            
          const bakeACake = async () => {
  const ingredients = await getIngredients()
  // do something with the ingredients, no more ".then" ๐Ÿ™Œ
}

Wow! ๐Ÿ˜Ž How much cleaner is that?

The use of async and await makes our code procedural and comprehensive. It looks cleaner and does exactly the same thing. Itโ€™s important to remember here that we arenโ€™t replacing Promises, we're still using them under the hood. Now we're using them with a new cleaner syntax.

And yes, this works with Promise.all too. So if we had done the shopping online, our code gets even smaller.


            javascript
            
          const getIngredients = async () => {
  const ingredients = await Promise.all([
    getButter(),
    getFlour(),
    getSugar(),
    getEggs(),
  ])
  return ingredients
}

We don't need that wrapper function anymore!


            javascript
            
          const getIngredients = async () =>
  await Promise.all([getButter(), getFlour(), getSugar(), getEggs()]);

Awaiting a non-Promise permalink

How about if the value you await on is not a Promise? In our example, the asynchronous functions are returning a String after a setTimeout.


            javascript
            
          const egg = await ๐Ÿฅš

There will be no error, the value becomes a resolved Promise ๐Ÿ˜…

What about rejections? permalink

Up until now, weโ€™ve dealt with the happy path ๐Ÿ˜ƒ But how about in the case where a Promise rejects?

For example, what if there are no eggs in stock? Our asynchronous function for getEggs would reject with a potential error.

To accommodate for this, a simple try/catch statement will do the trick ๐Ÿ‘


            javascript
            
          const getIngredients = async () => {
  try {
    const butter = await 'Butter'
    const flour = await getFlour()
    const sugar = await getSugar()
    const eggs = await getEggs()
    return [
      butter,
      flour,
      sugar,
      eggs,
    ]
  } catch(e) { return e }
}

We could wrap at this level or higher up where we invoke getIngredients ๐Ÿ‘

Consuming our function and baking the cake ๐Ÿฐ permalink

If youโ€™ve got this far, weโ€™ve created our function for getIngredients with the new async + await keywords. What might the rest of it look like?


            javascript
            
          const bakeACake = async () => {
  try {
    // get the ingredients
    const ingredients = await getIngredients()
    // mix them together
    const cakeMix = await mix(ingredients)
    // put in oven on 180C, gas mark 4for 20-25 minutes
    const hotCake = await cook(cakeMix)
    // allow to stand before serving
    const cake = await stand(hotCake)
    return cake
  } catch (e) { return e }
}

Much cleaner than what we might have done previously with Promises ๐ŸŽ‰

Thatโ€™s it! Baking a cake with async + await in 5 minutes ๐Ÿฐ permalink

If youโ€™ve got this far, thanks for reading ๐Ÿ˜ƒ Iโ€™ve put together a gist with some possible example code that can be seen below along with some further resources on async + await.

The important takeaways โš ๏ธ;

  • async functions will always return a Promise
  • await will in most cases be used against a Promise or a group of Promises
  • Handle any potential errors with a try/catch statement ๐Ÿ‘
  • We havenโ€™t touched on this but you can await an await. Making a fetch request you might await the request and then await the json function.

            javascript
            
          const data = await (await fetch(`${dataUrl}`)).json()

As always, any questions or suggestions, please feel free to leave a response or tweet me ๐Ÿฆ! Be sure to follow me on the socials ๐Ÿ˜Ž


            javascript
            
          const PROB = 0.2
const grabIngredient = ingredient => () => {
	return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (Math.random() > PROB) {
            resolve(ingredient)
        } else {
        	reject(`Sorry, we've got no ${ingredient}`)
        }
      }, Math.random() * 1000)
    })
}


// boilerplate functions for getting the different ingredients
const getButter = grabIngredient('Butter')
const getFlour = grabIngredient('Flour')
const getSugar = grabIngredient('Sugar')
const getEggs = grabIngredient('Eggs')

const getIngredientsFromTheSuperMarket = async () => {
  try {
    const butter = await getButter()
    const flour = await getFlour()
    const sugar = await getSugar()
    const eggs = await getEggs()
    return [
      butter,
      flour,
      sugar,
      eggs,
    ]
  } catch(e) { return e }
}

const getIngredientsOnline = async () => await Promise.all([
  getButter(),
  getFlour(),
  getSugar(),
  getEggs(),
])

// boilerplate async functions that return strings
const mix = async (ingredients) => `Mixing ${ingredients}`
const cook = async (cakeMix) => 'Hot Cake'
const stand = async (hotCake) => '๐Ÿฐ'

const bakeACake = async () => {
  try {
    const ingredients = await getIngredientsOnline()
    const cakeMix = await mix(ingredients)
    const hotCake = await cook(cakeMix)
    const cake = await stand(hotCake)
    console.info('BAKED', cake)
    return cake
  } catch (e) { console.info(e) }
}

bakeACake()

Further resources permalink