Back button to all articlesAll articles

The dangers of async/await

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. For example, consider this Vue code snippet:

1 async initStore(query) { 2 await this.getConfig(); 3 await this.getUser(); 4 await this.checkRussianContext(query); 5 6 await this.getBasket(this.$store.state.config.selectedCurrency), 7 8 await this.$store.dispatch('options/fetchOptions', { 9 basket : this.$store.state.basket, 10 }); 11 }, 12

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:

1const getUserData = async () => { 2 // Get a random dog as our user's avatar 3 const res = await fetch("https://dog.ceo/api/breeds/image/random"); 4 const { message } = await res.json(); 5 6 // Get our user's general data 7 const user = await fetch("https://randomuser.me/api/"); 8 const { results } = await user.json(); 9 10 // ... 11}; 12

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.

1const getUserDataFaster = async () => { 2 // Execute both requests in parallel 3 const [res, user] = await Promise.all([ 4 fetch("https://dog.ceo/api/breeds/image/random"), 5 fetch("https://randomuser.me/api/"), 6 ]); 7 const [{ message }, { results }] = await Promise.all([ 8 res.json(), 9 user.json(), 10 ]); 11 12 // ... 13}; 14

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:

1 async initStore(query) { 2 await Promise.all([ 3 await this.getConfig(), 4 await this.getUser(), 5 await this.checkRussianContext(query) 6 ]) 7 8 await this.getBasket(this.$store.state.config.selectedCurrency), 9 10 await this.$store.dispatch('options/fetchOptions', { 11 basket : this.$store.state.basket, 12 }); 13 14 await initBooking() 15 }, 16

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.

1 async initStore(query) { 2 await Promise.all([ 3 await this.getConfig(), 4 await this.getUser(), 5 await this.checkRussianContext(query) 6 ]) 7 8 this.getBasket(this.$store.state.config.selectedCurrency).then(async () => { 9 await this.$store.dispatch('options/fetchOptions', { 10 basket : this.$store.state.basket, 11 }); 12 }) 13 14 await initBooking() 15 }, 16

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 twice before using them !

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 ❤️