Async / await can slow down your code

Matej Kormuth
Frontend Weekly
Published in
3 min readOct 3, 2017

This article is about my experience with async / await keywords I recently started using as Node 8.0.0+ fully supports it (mainly in TypeScript but I think the whole concept applies to ES6 too).

Async/await makes asynchronous code look and behave a little more like synchronous code. This is where all its power lies.

Using async /await is simple. You just create async function and call other async functions with await keyword before the calls.

Simple await usage inside the async function.

This way everything after the await and call behaves as if it was placed inside the then() handler of Promise returned by downloadData().

It could have also been implemented as classic callback as shown in the following example:

The same code rewritten with callback style.

Because its syntax is really nice and simple it’s really easy to get used to await all of your network and I/O calls.

However, you should be careful when using it multiple times in a row as the await keyword stops execution of all the code after it. (Exactly as it would be in synchronous code)

In the next example the code is really clean and nice (in contrast to callback hell) but it silently became serial too.

That means the downloadFromService2() method wouldn’t be called and the second request wouldn’t be sent before data is downloaded from service 1.

Also the function downloadFromService3() wouldn’t be called before the downloadFromService2() is completed.

Imagine if all the services had ~100ms response time. Then the whole processData() method would take more than ~300ms to complete!

Three requests are serially made in this API call and thus it has really poor performance.

This is not what we want and expect from “event-driven, non-blocking I/O model”. There is also absolutely no need to wait for the completion of first request as none of other requests depend on its result.

We would like to have requests sent in parallel and wait for all of them to finish simultaneously. This is where the power of asynchronous event-driven programming lies.

To fix this we can use Promise.all() method. We save Promises from async function calls to variables, combine them to an array and await them all at once.

However this works only if all our requests are non-dependent.

Usually in real life some of our async calls are dependent on results of previous calls and some are not.

We can use the following rules when we want to aim for the smallest possible latency / request time:

  1. call async functions as soon as possible and save returned Promise(s) to variable(s)
  2. await saved Promises as late as possible

Real life usage is illustrated in following example of removing the post from database.

Firstly we make all async call we can. In this case it means: making a call to database to fetch Post object and also making a call to permission service.

Then we await the permissionsPromise because the next thing we should do is to check if the user can actually remove posts.

While we are waiting for the permission check to complete, the Post object is being fetched from database in background.

If the permission check succeeded we proceed by getting the Post object. We do this by awaiting postPromise and chances are that the Post is already fetched.
If that’s the case, await will return immediately.

We than simply call sendRemoveRequest() with fetched Post object.

Most of the requests are happening in parallel in this API call and so the request has lower latency.

By using await keyword appropriately we managed to save about 200ms from each remove_post request. Briliant!

--

--