DEV Community

Cover image for JavaScript's Async + Await in 5 Minutes
Jhey Tompkins
Jhey Tompkins

Posted on

JavaScript's Async + Await in 5 Minutes

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 🍰

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:

const getButter = () => new Promise((resolve, reject) => {
  setTimeout(() => resolve('Butter'), 3000)
})
Enter fullscreen mode Exit fullscreen mode

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

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 👀

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)
        })
      })
    })
  })
})
Enter fullscreen mode Exit fullscreen mode

This works but doesn’t look great 👎 It would look better with a Promise chain.

const getIngredients = () => getButter()
  .then(updateBasket)
  .then(getFlour)
  .then(updateBasket)
  .then(getSugar)
  .then(updateBasket)
  .then(getEggs)
  .then(updateBasket)
Enter fullscreen mode Exit fullscreen mode

If we were doing our grocery shopping online, we could use Promise.all 🤓

const getIngredients = () => Promise.all([
  getButter(),
  getFlour(),
  getSugar(),
  getEggs(),
])
Enter fullscreen mode Exit fullscreen mode

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

getIngredients().then(ingredients => doSomethingWithIngredients(ingredients))
Enter fullscreen mode Exit fullscreen mode

Tidying it up with async + await

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

const getIngredients = async () => {}
Enter fullscreen mode Exit fullscreen mode

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.

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

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.

const bakeACake = async () => {
  const ingredients = await getIngredients()
  // do something with the ingredients, no more ".then" 🙌
}
Enter fullscreen mode Exit fullscreen mode

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.

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

We don't need that wrapper function anymore!

const getIngredients = async () =>
  await Promise.all([getButter(), getFlour(), getSugar(), getEggs()]);
Enter fullscreen mode Exit fullscreen mode

Awaiting a non-Promise

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.

const egg = await 🥚
Enter fullscreen mode Exit fullscreen mode

There will be no error, the value becomes a resolved Promise 😅

What about rejections?

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 👍

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 }
}
Enter fullscreen mode Exit fullscreen mode

We could wrap at this level or higher up where we invoke getIngredients 👍

Consuming our function and baking the cake 🍰

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?

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 }
}
Enter fullscreen mode Exit fullscreen mode

Much cleaner than what we might have done previously with Promises 🎉

That’s it! Baking a cake with async + await in 5 minutes 🍰

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.
const data = await (await fetch(`${dataUrl}`)).json()
Enter fullscreen mode Exit fullscreen mode

As always, any questions or suggestions, please feel free to leave a response or tweet me 🐦! Be sure to follow me on the socials 😎

Further resources

Top comments (1)

Collapse
 
savagepixie profile image
Info Comment hidden by post author - thread only accessible via permalink
SavagePixie

Wow! 😎 How much cleaner is that?

Not much, honestly. You're filling the local scope with a bunch of variable names of which you need to keep track. In a simple example, there's hardly any difference. In a complex one, I'd rather not have a bunch of variables there if I can avoid it.

I think that your article could be greatly improved if you used examples of clean code written using promises. As it is now, the proper conclusion to your article is "Well, you can only claim that because you're using bad examples of promises." Arguing for async/await is all good and that, but if you want to make the point that it's clearer than promises, at least use cleanly written code.

I really don't understand why you're wrapping everything in a Promise. Assuming that you're various getIngredient functions are asynchronous (which judging by your async/await examples they are), you don't need to write this:

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

You can simply do:

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

Similarly, you don't need to wrap a Promise.all within another promise. Instead of this:

const getIngredients = () => new Promise((resolve, reject) => {
  Promise.all([
    getButter(),
    getFlour(),
    getSugar(),
    getEggs(),
  ]).then((ingredients) => resolve(ingredients))
})

You can use this:

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

Which has the advantage of not needing to store the stuff in a variable and return it, as the return will be immediately available in the following then statement.

And again, if you're going to claim that async/await is much cleaner than promises, at the very least don't use code like this:

const bakeACake = () => new Promise((resolve, reject) => {
  getIngredients()
    .then((ingredients) => {
      return mix(ingredients)
    })
    .then((cakeMix) => {
      return cook(cakeMix)
    })
    .then((hotCake) => {
      return stand(hotCake)
    })
   .then((cake) => resolve(cake))
   .catch((e) => reject(e))
})

Because that can easily be written like this:

const bakeACake = () => getIngredients()
    .then(mix)
    .then(cook)
    .then(stand)
    .catch(e => e)

Some comments have been hidden by the post's author - find out more