Photo by Yulia Matvienko on Unsplash
Await. And then? #2/4: the async/await brothers
Await, we were waiting for you!
In the previous bite, we detected three major issues with .then
, here they are:
return
statements are easy to forget, breaking promise chains.then
calls can lead to callback helleven if we pay attention to the aforementioned points, the symmetry of
.catch
/catch
and.finally
/finally
is unnatural
Let's see how async
/await
helps us fix those problems.
Broken promise chains
Take this sample code from the previous bite. The promise chain is broken as we missed a return
statement.
function goToGym() {
doWorkout().then(() => console.log("I finished my workout!"))
}
goToGym().then(() => console.log("Back home"))
With async
/await
it changes to:
async function goToGym() {
await doWorkout()
console.log("I finished my workout!")
}
await goToGym()
console.log("Back home")
An async
function must always return a promise and when a value is returned it will always be wrapped in a resolved promise. When there is no explicit return value, then the value undefined
is assumed and the promise is resolved with it. In short, the code above is equivalent to:
async function goToGym() {
await doWorkout()
console.log("I finished my workout!")
return undefined
}
await goToGym()
console.log("Back home")
So there is no chance we can break our promise chain! Neat!
Nested callbacks
Take this example from the previous bite.
function giveMeFruit() {
const fruit = []
return fetchBananas()
.then(bananas => {
fruit.push(...bananas)
// supposing we need bananas to fetch apples
return fetchApples(bananas)
})
.then(apples => {
fruit.push(...apples)
// supposing we need apples to fetch pineapples
return fetchPinapples(apples)
})
.then(pineapples => {
fruit.push(...pineapples)
return fruit
})
}
giveMeFruit().then(fruit => console.log("My fruit!", fruit));
We cannot take advantage of Promise.all
, as there is a cascade dependency between promises. So let's see how async
/await
shuffles the card on the table.
function giveMeFruit() {
const bananas = await fetchBananas();
const apples = await fetchApples(bananas);
const pineapples = await fetchPinapples(apples);
}
const fruit = await giveMeFruit();
console.log("My fruit!", fruit);
That's a lot of improvement in terms of nesting. First, every time we need to add a new fetch, we just add a line. Secondly, we got rid of that fruit
array.
Error handling symmetry
And what about error handling?
function giveMeFruit() {
const bananas = await fetchBananas();
const apples = await fetchApples(bananas);
const pineapples = await fetchPinapples(apples);
}
try {
const fruit = await giveMeFruit();
console.log("My fruit!", fruit);
} catch (err) {
console.error(`BOOM`, err);
}
This is super neat! We also got rid of the .catch
/catch
and .finally
/finally
symmetry, now using solely the standard JavaScript.
Conclusions
Let's briefly recap the advantages of async
/await
over .then
No room for broken promise chains
No more nesting
Use of standard JavaScript syntax for
catch
andfinally
At this point, it seems quite reasonable to always use async
/await
everywhere in every situation. Unfortunately, this is only 99% true.
In the next bite, we will better highlight the difference between the two approaches and pave the way to demonstrate how .then
still covers cases that async
/await
don't.