Blocking asynchronous JavaScript

Szabolcs Damján
Byborg Engineering
Published in
4 min readJul 13, 2021

--

Eh… what?

Photo by Gabriel Gusmao on Unsplash

Recently, I participated in several frontend developer interviews. Unfortunately, the sad truth is that most candidates are confused about how “asynchronous” things work in JavaScript, what is considered as blocking or non-blocking code, and how (a)synchronicity relates to these.

Let’s summarize the most common misconceptions and clarify them.

JavaScript is single threaded by default. This means that the operations defined by your code are executed one after the other, in the default setup there is no real parallelism at all. We will come back a little bit later to this statement, because there are ways to run your code in parallel in real multi-threaded way, but it’s an advanced use case.

A blocking code means that if the main thread is occupied with a certain task, all the other “todo’s” will be put to the side. From a user experience point of view, this is something we want to avoid, because a blocking task will obviously prohibit all kinds of user interaction.

And what about the aforementioned asynchronous code?

Asynchronous codes are usually run after an event, like a network response, or after a certain delay. In a nutshell: the JS engine adds these so-called “callbacks” (all callbacks, promises, and micro tasks) to certain event queues (promises and micro tasks have separate queues), then runs them after processing the current functions on the main thread.

Asynchronous code is a great tool to wait for events without blocking the main thread. For example, if we use asynchronous code, our functions will not be blocked by waiting for a network response, rather the callback function will process the response as soon as it arrives.

However, please note that long-running tasks will block any main threads, regardless if they are executed regularly or as a callback.

Let’s see some examples of the user experience blocking scenario. In the following example, you will see two rotating text items: CSS manages the rotation of one of them, which will be unaffected by the blocking code because the browser runs it totally separately. A script rotates the other one and a simple “setTimeout” callback modifies the angle of the text every 100 ms. You will see that this rotation script will be blocked when the main thread becomes occupied by a long running operation started by user activity.

There are several ways to run asynchronous code, but they will still block your main thread and thus, hamper user experience as well.

Some examples:

  • promise
  • async function
  • setTimeout callback

In the following example, we ran the same long running task with async function, and with a “setTimeout” callback — both seem to be blocking…

Generator functions can be used to “fake” parallel operations, but a long running operation will still block them, and this way the whole single threaded operation.

Going parallel

However, there is a way to truly run your computation heavy functions in parallel to each other (essentially on separate processor threads).

It’s possible by using the web worker API of modern web browsers, and also possible in node.js as well, but with a different API.

Now, let’s see in our final example if we are still blocking the UI… and no!

Wrap it up

When you run asynchronous code, the engine only executes the function when a certain event occurs (like a server response) or when it finishes its current tasks — like the (in)famous setTimeout with 0 sec delay.

When you’re faced with the need for a long-running operation, it’s better to set it up in a separate thread or to split it up into chunks. This way, you will be able to keep your user interface’s response time low.

--

--