After a few months consulting on the rewriting of a large-scale application, I've come to realize that async
/await
was used de facto for most asynchronous operation and parallel executions seemed to be out of the picture. For example, consider this Vue code snippet:
async initStore(query) {
await this.getConfig();
await this.getUser();
await this.checkRussianContext(query);
await this.getBasket(this.$store.state.config.selectedCurrency),
await this.$store.dispatch('options/fetchOptions', {
basket : this.$store.state.basket,
});
},
Here, each line of code is executed when its predecessor is completed. Meaning getUser
will wait for getConfig
to finish fetching data before being executed.
Here are a few points that come to mind when seeing this snippet:
- What if one line does not need data from the previous one? Why block its execution and slow down our application?
- Could we run unrelated methods in parallel using something like
Promise.all
? - Related methods should probably be using a
then
block to avoid blocking the rest of the method
The point this article will be to help you catch this code smell by showing you that using async
/await
by default in some cases can have a drastic impact on performance and UX.
Unrelated queries should be executed in parallel
Let's see some concrete data, shall we?
Here's the code snippet we'll be analyzing:
const getUserData = async () => {
// Get a random dog as our user's avatar
const res = await fetch('https://dog.ceo/api/breeds/image/random')
const { message } = await res.json()
// Get our user's general data
const user = await fetch('https://randomuser.me/api/')
const { results } = await user.json()
// ...
}
Running this snippet 100 times on fast 3G (using Chrome's dev tools), the average execution time is 1231.10ms.
But why block the second query when it doesn't need the result of the first? Let's change our code to the following and re-run it 100 times.
const getUserDataFaster = async () => {
// Execute both requests in parallel
const [res, user] = await Promise.all([
fetch('https://dog.ceo/api/breeds/image/random'),
fetch('https://randomuser.me/api/')
])
const [{ message }, { results }] = await Promise.all([res.json(), user.json()])
// ...
}
We now have an average execution time of 612.50ms, half the time needed when both queries were executed one after the other.
The point is: if you can execute time-consuming queries in parallel, do it.
Try it out yourself on this codepen.
Unrelated code should not have to wait
Let's take my first example but with a twist:
async initStore(query) {
await Promise.all([
this.getConfig(),
this.getUser(),
this.checkRussianContext(query)
])
await this.getBasket(this.$store.state.config.selectedCurrency),
await this.$store.dispatch('options/fetchOptions', {
basket : this.$store.state.basket,
});
await initBooking()
},
Here, the first 3 requests are executed in parallel, whereas the next ones rely on data fetched beforehand and will therefore be executed afterwards. Although this snippet poses a problem, did you spot it?
Poor little initBooking
will have to wait for both getBasket
and fetchOptions
to finish before executing even though it has nothing to do with the data they'll fetch.
An easy solution is to trade the await
with a simple then
block.
async initStore(query) {
await Promise.all([
this.getConfig(),
this.getUser(),
this.checkRussianContext(query)
])
this.getBasket(this.$store.state.config.selectedCurrency).then(async () => {
await this.$store.dispatch('options/fetchOptions', {
basket : this.$store.state.basket,
});
})
await initBooking()
},
This way, both getBasket
and initBooking
will be executed alongside one another.
Want to see it for yourself? Check out this codepen illustrating my example.
I'll stop the article there so I don't overload you with examples, but you should get the gist of it by now.
async
/await
are wonderful additions to the Javascript language but I hope you'll now ask yourself if they have their place in the specific method you're working on and more importantly: if some of your queries could be executed in parallel.
Thank you for reading, I'd love it if you gave me a follow on Twitter @christo_kade, this way we'll get to share our mutual skepticism towards awaits
❤️