How JavaScript’s Asynchronous Operations Work in the Browser

Emma Delaney
5 min readMay 20, 2024

--

Before we get into the details of the JavaScript runtime and running asynchronous code tasks in the background, let’s cover the basics first. JavaScript is a single-threaded language. This means you only have one call stack and a lot of memory. Therefore, you can only run one code at a time. In other words, the code runs correctly. You must execute the code on the call stack before moving on to the next code to execute. There are two types of code activity in JavaScript: asynchronous code, which runs after a certain load, and synchronous code, which runs immediately. Let’s understand the difference between synchronous and asynchronous code before we continue.

Synchronous code:

  • Most code is synchronous.
  • It is executed line by line, that is, each line of code waits for the previous line to complete its execution.
  • Long-running code operations block code execution for future batch code executions.

Asynchronous code:

  • Asynchronous the code is not synchronous. That is, the code runs as soon as a task running in the background completes.
  • It is non-blocking in nature. Execution does not wait for an asynchronous task to complete its work.
  • Callback functions alone do not make code asynchronous.

Runtime:

The runtime is the environment in which a programming language runs. The JavaScript runtime is primarily made up of three elements: the JavaScript engine, the Web API, and the call stack. JavaScript can work with both asynchronous and synchronous code.

The unique feature of the JavaScript runtime environment is that the JavaScript interpreter is single-threaded. However, you can run multiple codes at concurrent fashion in a non-blocking way. This allows for asynchronous behaviour. Since the interpreter is not multi-threaded, parallelism is impossible. Let us understand what is the difference between parallelism and concurrency.

Concurrency

In this approach, tasks are executed and completed in a nested manner. That is, tasks run simultaneously, but only one task at a time. This happens when tasks are broken down into small parts and managed quite well. This is also shown in the following figure.

Parallelism:

In contrast, in the parallelism approach, we can execute the tasks at the same level. This means that many tasks can run at any time, regardless of whether other tasks are running or not. This happens when we execute multithreaded tasks on different threads available to the interpreter.

After understanding that the JavaScript runtime follows concurrent execution, let’s understand how the code is different. It is intelligently implemented behind the scenes. To understand the execution process, we need to understand the structure of the JavaScript execution engine in detail.

JavaScript Engine:

The JavaScript engine can be used as The core of the term is considered. This is where all the code runs. The JavaScript engine consists of a heap and a call stack. Let’s understand each of them.

Stack:

It is the place where all objects and data are stored. This is similar to the heap memory we see in other languages like C++, Java, etc. Contains data storage for all objects, tables, etc. that we create in the code.

Call stack:

This is where the code is stacked before execution. It has the properties of a basic stack (first in, last out). Once pushed into the call stack, a coding task is executed. An event loop occurs, which forms the JavaScript interpreterelegant. It is responsible for concurrent behavior.

Web API:

JavaScript has access to several Web APIs and adds many functions. For example, JavaScript has access to the DOM API, which gives JavaScript access to the DOM tree. With this we can make changes to the HTML elements present in the browser. Also, you can think about the stopwatch, which gives access to time-related functions, etc. Also the geolocation API, which gives access to the browser’s location. This way, JavaScript has access to many other APIs.

Callback queue:

This is where the asynchronous code is queued before the propagation. to the call stack. The event loop handles the transition of code activity from the callback queue to the call stack. Furthermore, there is also a microtask queue.

Microtask queue:

The microtask queue is similar to the so-called feedback queue . but it has a higher execution priority than that. In other words, if there is a situation where the call stack is empty (except for the global execution context) and two tasks need to be executed, one from the microtask queue and one from the normal task queue or queue callback of tasks, then The code task present in the microtask queue has a higher priority than that.

After understanding the basic terminology, let’s quickly understand how the code works asynchronous.

How does asynchronous JavaScript work behind the scenes?

Here we learn the concept of event loops. In simple terms, an event loop can be defined as a clever technique for executing code from the callback queue, passing it to the call stack when it is empty (except the general context of the call).

The event loop decides when to execute each code task in the callback queue and microtask queue. Let’s understand the process of running the entire code in an imaginary situation. Let’s try to generalise the process into several phases:

All code activities present in the call stack are executed in an orderly manner. It is synchronous and waits for the previous code’s task to execute. During this step, all code tasks in the call stack are executed.

Once background loading is finished, the asynchronous task is sent to the queue callbacks. The callback function attached to this asynchronous task is here waiting to be executed. This asynchronous operation is then queued for execution in the callback queue.

Now the event loops part comes into play. The event loop continuously checks whether the call stack is empty, and once it detects that it is empty, it takes the first task from the callback queue and pushes it into the call stack, which is then executed. This process continues until the event loop determines that the call stack and callback queue are empty.

Do the promises also go to the callback queue?

No, let’s understand how they work behind the scenes. Promises are also a special type of asynchronous tasks that, after loading, are queued in a special place called a microtask queue. This microtask queue has a higher priority than the runtime callback queue. The event loop also looks for tasks in the microtask queue when it searches for tasks to execute in the callback queue. When it finds tasks to do, it assigns microtasks a higher priority and executes them first.

--

--