pexels gustavo fring 4149023

Javascript Asynchronous Programming Concepts

In this article, we examine some important concepts related to asynchronous programming and its functionality in web browsers and JavaScript. After reading this article, you will understand these concepts and you will be ready to move forward.

What does Asynchronous mean?

Normally, the code of a program is executed linearly and directly, and only one task is being done at any moment. If a function needs the result of another function to run, it must wait for the other function to finish its work and return the result, and until that happens, the entire program effectively stops from the user’s point of view.

For example, Mac users sometimes experience this situation in the form of a rotating rainbow cursor. By displaying this cursor, the operating system is actually announcing that the current program you are using should be stopped and waiting for something else to be done, and this will take some time, thereby announcing that there is no need to worry and that we are busy.

spinning wheel of death
The spinning wheel of death – Mac

This is an unpleasant experience and is not a good use of the computer’s processing power, especially in this age when computers have multiple processing cores. Waiting for another task to finish, when it can be left to another processing core to notify us when the task is finished. In this way, tasks can be carried out simultaneously, which forms the basis of “asynchronous programming”. It all depends on the application environment you are using to provide different APIs to enable the asymmetric execution of tasks. In the field of web programming, this environment is the web browser.

Blocking Code

Asynchronous techniques are very important in programming and especially web programming. When a web application is running in the browser and executes a bunch of heavy code without returning control to the browser, it can appear as if the browser is locked. This situation is called “blocking”. In this case, the browser cannot manage the user’s input and execute other tasks, and it will continue until the control is returned from the processor to the browser.

In the following, we will examine some examples to understand the exact meaning of blocking.

In this example, as you can see, an “event listener” has been added to the button to execute a timer operation when the button is clicked. In this operation, 10 million dates are calculated and the result is displayed on the console. A paragraph will then be added to the DOM.

const btn = document.querySelector('button');
btn.addEventListener('click', () => {
  let myDate;
  for(let i = 0; i < 10000000; i++) {
    let date = new Date();
    myDate = date
  }

  console.log(myDate);

  let pElem = document.createElement('p');
  pElem.textContent = 'This is a newly-added paragraph.';
  document.body.appendChild(pElem);
});

Browser Console

When you run this example, open the JavaScript console and then click the button, you’ll notice that the paragraph doesn’t appear on the page unless the dates are calculated and the final message is displayed in the console. This code is executed as written, and the next operation will not be executed until the previous operation has finished.

Note: The previous example is very unrealistic. We never calculate a million dates in a web application. This example is provided only to give you an initial idea of ​​the topic.

In the second example, we simulate something that is a little more realistic and more likely to be seen on a web page. We block user interactivity through UI rendering. In this example we have two buttons:

  • A “Fill canvas” button that, when clicked, renders 1 million blue circles on the <canvas>.
  • A “Click me for alert” button displays an alert message when clicked.
function expensiveOperation() {
  for(let i = 0; i < 1000000; i++) {
    ctx.fillStyle = 'rgba(0,0,255, 0.2)';
    ctx.beginPath();
    ctx.arc(random(0, canvas.width), random(0, canvas.height), 10, degToRad(0), degToRad(360), false);
    ctx.fill()
  }
}

fillBtn.addEventListener('click', expensiveOperation);

alertBtn.addEventListener('click', () =>
  alert('You clicked me!')
);

If you click the first button and then quickly click the second button, you’ll notice that the warning doesn’t appear until the circles have finished rendering. The first operation blocks the second operation until its execution is finished.

Note: In this case the code is ugly and our intention was just to create blocking mode, but this is a common problem that real web application developers face all the time.

The reason for this situation is that JavaScript is generally a “single-threaded” programming language. At this stage, we must first get acquainted with the concept of “thread” in programming.

Thread

A thread is basically a single process that an application can call upon to perform a task. Each thread can execute only one single task at a time:

Task A --> Task B --> Task C

Each task is executed sequentially, and in order for a task to start, the previous task must be completed.

As we said earlier, many computers now have multi-core processors and can therefore run multiple tasks at the same time. Programming languages ​​that can support multiple threads can use multiple processing cores to execute tasks simultaneously.

Thread 1: Task A --> Task B
Thread 2: Task C --> Task D

JavaScript is a Single-Threaded Language

JavaScript is traditionally a single-threaded language. Even if you use multiple processing cores, you still need to execute tasks on a single thread, called the “main thread”. Thus, the above example is executed as follows:

Main thread: Render circles to canvas --> Display alert()

Of course, after some time JavaScript got help from some tools to solve such problems. “Web worker” (Web worker) provides the possibility of sending multiple JavaScript processes to a separate thread called worker. In this way, several categories of JavaScript code can be executed at the same time.

In general, a worker is used to perform heavy processing and remove this task from the main thread. In this way, the user’s interaction with the browser is not blocked.

Main thread: Task A --> Task C
Worker thread: Expensive task B

Asynchronous Code

Web workers are quite useful, but they also have their limitations. The most important limitation of web workers is that they cannot access the DOM. So you can’t expect a web worker to do anything directly to update the UI. We can’t render 1 million blue circles inside the worker, because its job is purely computational.

The second problem is that although the code is not blocking a worker, it is still inherently synchronous. This situation will be problematic in cases where a function needs the results of previous processes. Consider the following:

Main thread: Task A --> Task B

In this case, task A does something like fetch an image from the server, and task B does something like apply a filter to the image. If you start task A first and then immediately start task B, you will get an error because the image is not ready yet.

Main thread: Task A --> Task B --> |Task D|
Worker thread: Task C -----------> | |

In this case, suppose task D uses the results of both tasks B and C. If we can guarantee that both of these results will be ready at the same time, then there will be no problem, but this situation is unlikely. Task D will fail if it tries to run when its input is not yet ready.

Asynchronous Browser Capabilities

Browsers allow us to perform some operations asynchronously to correct such errors. Features such as promises make it possible to specify the execution state for an operation (for example fetching an image from the server) and then wait for the result to be returned before executing the next operation:

Main thread: Task A Task B
Promise: |__async operation__|

Because the operation happens elsewhere, the main thread is not blocked while the asynchronous operation is being processed. In the next part of this series of educational articles, we will examine the methods of writing asynchronous codes.

Conclusion

Modern software design principles are increasingly developed around the use of asynchronous programming to allow programs to perform multiple tasks simultaneously.

As you use more modern and powerful APIs, you’ll find more and more cases where asynchronous programming is the only way to execute a task.

Writing asynchronous code is more difficult and takes some getting used to, but it will get easier over time.