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 Promise
s ๐ญ They aid in understanding the flow of your code. There are no new concepts, itโs Promise
s 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
:
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 ๐
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.
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
๐ค
const getIngredients = () => Promise.all([
getButter(),
getFlour(),
getSugar(),
getEggs(),
])
These look much tidier but we still need to use a callback to get those ingredients.
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
const getIngredients = async () => {}
Now, how might those Promise
s 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,
]
}
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" ๐
}
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 Promise
s, 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
}
We don't need that wrapper function anymore!
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
.
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 ๐
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?
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 Promise
s ๐
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 aPromise
await
will in most cases be used against aPromise
or a group ofPromise
s- Handle any potential errors with a
try
/catch
statement ๐ - We havenโt touched on this but you can
await
anawait
. Making afetch
request you mightawait
the request and thenawait
thejson
function.
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 ๐
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
await
โ MDNasync
function โ MDN- Async + Await podcast โ Syntax.fm