Understanding async-await in JavaScript

Rules of thumb and examples for when and how to use async and await

Gokul N K
Gokul N K
Jul 20, 2018 · 8 min read
Photo by Qusai Akoud on Unsplash

async and await are extensions of promises. If you are not comfortable with the basics of promises, please spend some time understanding promises before reading further.

I am sure that many of you are using async and await already. But, I think it deserves a little more attention.

Here is a small test: If you can’t spot the problem with the below code, read on.

We will revisit this code block later, once we have gone through async-await basics. As always, Mozilla docs is your friend. Especially check out the definitions.

From MDN:

“An asynchronous function is a function which operates asynchronously via the event loop, using an implicit Promise to return its result. But the syntax and structure of your code using async functions is much more like using standard synchronous functions.”

I wonder who writes these descriptions. They are concise and well-articulated. To break it down:

  1. The function operates asynchronously via an event loop.
  2. It uses an implicit Promise to return the result.
  3. The syntax and structure of the code are similar to writing synchronous functions.

And MDN goes on to say:

“An async function can contain an await expression that pauses the execution of the async function and waits for the passed Promise's resolution, and then resumes the async function's execution and returns the resolved value. Remember, the await keyword is only valid inside async functions.”

Let’s jump into code to understand this better. We will reuse the three functions we used for understanding promises here as well.

A function that returns a promise which resolves or rejects after n number of seconds.

Two more deterministic functions. One that resolves after n seconds and another which rejects after n seconds.

As these three functions return promises, we can also call these functions as asynchronous functions. See, we wrote async functions before we even knew about them.

If we had to use the function promiseTRSANSG using a standard format of promises, we would have written something like this:

There is a lot of unnecessary code here, for example, the anonymous function just for assigning the handlers. What async-await does, is improve the syntax for this which makes it seem more like synchronous code.

If we had to the same as the above in async await, format it would be like this:

Well, that looks much more readable than the standard promise syntax.

When we used await, the execution of the code was blocked. That is the reason that you had the value of the promise resolution in the variable result.

As you can make out from the above code sample, instead of the .then part, the result is assigned to the variable directly when you use await.

You can also make out that the .catch part is not present here. That is because that is handled using try catch error handling. So, instead of using promiseTRSANS let us use promiseTRRARNOSG.

As this function can either resolve or reject, we need to handle both scenarios.

In the above code, we wrote two lines to give you an easy comparison between the standard format and async await format. The example in the next section gives you a better idea of the format and structure.


General Syntax of Using async-await

From the above code example you can make out that, instead of using the promise-specific error handling, we are using the more generic approach of using try catch for error handling.

That is one less thing for us to remember and it also improves the overall readability, even after considering the try catch block around our code.

So, based on the level of error handling you need you can add any number of catch blocks and make the error messages more specific and meaningful.


Pitfalls of Using async and await

async await makes it much easier to use promises. Developers from synchronous programming backgrounds will feel at home while using async and await.

This should also alert us, as this means that we are moving towards a more synchronous approach if we don’t watch out.

The whole point of JavaScript/Node.js is to think asynchronous by default, and not as an after-thought. async await generally means you are doing things in a sequential way. So, make a conscious decision whenever you want to use to async await.

Let’s start analyzing the code that I showed you in the beginning.

This seems like a harmless piece of code that fetches the GitHub details of three users: “nkgokul”, “BrendanEich”, and “gaearon”.

Right. That is true. That is what this function does. But it also has some unintended consequences.

Before diving further into the code, let’s build a simple timer.

Now, we can use executingAt wherever we want to print the number of seconds that have surpassed since the beginning.

Check out the output:

async-await analyzed

As you can find out from the output, each of the await functions is called after the previous function is completed. We are trying to fetch the details of three different users: “nkgokul”, “BrendanEich”, and “gaearon”.

It is pretty obvious that the output of one API call is in no way dependent on the output of the others.

The only dependency we have is these two lines of code.

We can create the userDetailsJSON object only after getting the userDetails.

Hence, it makes sense to use await here, which is within the scope of getting the details of a single user. So, let us make an async for getting the details of the single user.

Now that the fetchSingleUsersDetailsWithStats is async, we can use this function to fetch the details of the different users in parallel.

When you want to run things in parallel, the thumb rule that I follow is:

When we put all of this together we get:

The output for this is:

Promises run in parallel with timestamps

As you can make out from the output, promise creations are almost instantaneous, whereas API calls take some time.

We need to stress this as time taken for promise creation and processing is trivial when compared to IO operations.

So, when you’re choosing a promise library, it makes more sense to choose a library that is feature-rich and has better dev experience. As we are using Promise.all, all the API calls were run in parallel.

Each API takes almost 0.88 seconds. But, as they are called in parallel, we were able to get the results of all API calls in 0.89 seconds.

Understanding this should serve us well in most scenarios. Now, you can either skip to the thumb rules, or keep reading if you want to dig deeper.

Digging deeper into await!

For this, let us pretty much limit ourselves to the promiseTRSANSG function. The outcome of this function is more deterministic and will help us identify the differences.


Sequential Execution

Sequential Execution

Parallel Execution Using Promise.all

Parallel execution using promises

Concurrent Start of Execution

Asynchronous execution starts as soon as the promise is created. await just blocks the code within the async function until the promise is resolved.

Let’s create a function which will help us clearly understand this.

Concurrent start and then await

From a previous post, we know that .then is event-driven. That is, .then is executed as soon as the promise is resolved.

So, let’s use resolveAfter3seconds.then and resolveAfter4seconds.then to identify when our promises are actually resolved. From the output, we can see that resolveAfter3seconds is resolved after 3 seconds and resolveAfter4seconds is executed after 4 seconds. This is as expected.

Now, to check how await affects the execution of code, we used:

As we have seen from the output of .then, resolveAfter3seconds resolved one second before resolveAfter4seconds. But, we have the await for resolveAfter4seconds and then followed by await for resolveAfter3seconds.

From the output, we can see that although resolveAfter3seconds was already resolved it got printed only after the output of console.log(await resolveAfter4seconds); was printed.

Which reiterates what we said earlier. await only blocks the execution of the next lines of code in an async function and doesn’t affect the promise execution.


Disclaimer

MDN documentation mentions that Promise.all is still serial and using .then is truly parallel. I have not been able to understand the difference and would love to hear if anybody has figured out the difference.


Thumb Rules

Here is a list of thumb rules for using async and await:

  1. async functions return a promise.
  2. async functions use an implicit Promise to return results. Even if you don’t return a promise explicitly, the async function makes sure that your code is passed through a promise.
  3. await blocks the code execution within the async function, of which it (await statement) is a part.
  4. There can be multiple await statements within a single async function.
  5. When using async await, make sure to use try catch for error handling.
  6. If your code contains blocking code it is better to make it an async function. By doing this you are making sure that somebody else can use your function asynchronously.
  7. By making async functions out of blocking code, you are enabling the user (who will call your function) to decide on the level of asynchronicity they want.
  8. Be extra careful when using await within loops and iterators. You might fall into the trap of writing sequentially executing code when it could have been easily done in parallel.
  9. await is always for a single promise. If you want to await multiple promises (run this promise in parallel) create an array of promises and then pass it to the Promise.all function.
  10. Promise creation starts the execution of asynchronous functionality.
  11. await only blocks the code execution within the async function. It only makes sure that the next line is executed when the promise resolves. So, if an asynchronous activity has already started, await will not have an effect on it.
  12. When to use promises and when to use async-await?

Please point out if I am missing something here or if something can be improved.

Better Programming

Advice for programmers.

Gokul N K

Written by

Gokul N K

Inquisitive, Student, Teacher and a wanna be Story teller. Working on https://learningpaths.io/

Better Programming

Advice for programmers.

More From Medium

More from Better Programming

More from Better Programming

More from Better Programming

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade