DEV Community

Cover image for Javascript is single or multi-threaded?
Vasil Vasilev
Vasil Vasilev

Posted on

Javascript is single or multi-threaded?

Javascript is a synchronous programming language. But it is also the language of the Web which requires asynchronous fetching of data, also we want user interactivity, namely, functions that are event driven and executed based on user behavior only.

Callbacks and Promises

We achieve this by creating pieces of code that do not execute immediately when the program initializes. Those pieces of code are also known as callbacks and promises. Initially, the most basic approach was with pure callbacks, but the very nature of function definitions lead to the so called 'callback hell' of nested callbacks. Thus, a more refined tool emerged - a promise. Nowadays, we also have the async/await as a popular alternative to the '.then()' resolving of promises. However this article's purpose is not to educate on the basics of dealing with asynchronous requests.
It attempts to delve into the nature of Javascript and explain how come asynchronous tools such as callbacks and promises are part of it.

So how come a single-threaded programming language have asynchronous tools at its disposal?

Javascript runtime

The straightforward answer is - Javascript runtime. Nowadays, Javascript is not only a scripting language with the simple purpose of toggling a button on a static web page, it a fully capable programming language. Thus, to understand how a single-threaded language can achieve concurrency, let's start with the following image.

Image description

We see a JS engine and a few additional features - Web API, Event loop and a Queue. When we say single thread, we refer to the JS engine. Simply said, this relates to the code you have written and its execution contexts, while the additional features 'create' an additional thread. So, the runtime consists of two parts - your code and some other external code both producing the whole application. So, modern Javascript always needs a runtime environment to execute and the most popular one is the browser. Another popular one is Node.js.

But now, let's look at the mechanism of a callback function, applying what we have learned about runtime environment already.

Callbacks

function fetchData(callback) {
  setTimeout(function () {
    const data = 'Async data';
    callback(data); // Execute the callback function with the data
  }, 1000);
}

function processData(data) {
  console.log('Received data: ', data);
}

fetchData(processData); // Pass the processData function as a callback
console.log('Fetching data...');
Enter fullscreen mode Exit fullscreen mode

The Line of Execution:

console.log('Fetching data...'); // This line is executed first.
console.log('Received data: Async data'); // This line is executed second.

A Step by Step Explanation:

  1. fetchData(processData) is triggered first
  2. setTimeout is called with a delay of 1000 milliseconds.
  3. While waiting for the timeout, the program continues by executing console.log('Fetching data...');
  4. After 1000 milliseconds (1 second), the callback function processData is executed with the data 'Async data'.
  5. Inside processData, the statement console.log('Received data:', data); is finally executed.

So, the final order of printing statements is:

  1. "Fetching data..."
  2. "Received data: Async data"

Thus, the "Fetching data..." console.log executes before the fetchData function.

How is this possible?

How does Javascript, which is single-threaded, 'know' to continue interpreting the code and execute the subsequent console.log("Fetching data..."), even though it has another task seemingly on standby (fetchData function).

As I said, when we say single-threaded, we refer to the execution mechanism of the JS engine. The Javascript engine is responsible for the execution contexts, namely, managing the memory heap and the call stack. The memory heap stores all the variables defined in our JS code while the call stack performs the operations (function execution).

Image description

So, back to the current problem, single-threaded means only one call stack. One call stack, in turn, means only one piece of code can be executed at a time.

In our case with console.log('Fetching data...') and fetchData function, given the nature of Javascript to be non-blocking, Javascript does not wait for the response of the callback, but moves on with the interpretation of the subsequent blocks of code.

Why does it not wait?

The answer - the callback fetchData function is 'extracted' from the call stack of the current main thread and logically the execution continues with console.log('Fetching data...').

But where is this callback 'extracted' to?

The general answer not only for callbacks is that any such asynchronous function utilizes the Web API by relying on the Event loop to manage its queue priority when to re-enter the main thread call stack.

In the case of callbacks, the Timer Web API executes the setTimeout and the Event loop puts the result of that function in the Tasks Queue. That is also why the delay set in setTimeout is known as a minimum delay time. It is unclear when exactly the call stack will be freed up so that a new event cycle can be executed and tasks from the queue be added to it.

Image description

Let's now look at how promises are resolved in the Javascript runtime.

Promises

const promise = new Promise(resolve=>{
  resolve("Promise")
}, reject => {

})

promise.then(res=>console.log(res))
Enter fullscreen mode Exit fullscreen mode

In the case of promises, the promise is set into the Microtask Queue which the event loop ranks with higher priority than regular tasks such as setTimeout callbacks. Meaning when/if a promise is fulfilled, its result is added to the microtask queue, ensuring that it will be executed before the next (regular) task in the event loop.

Image description

Inversion of control

Thus, with simple words, the Javascript runtime adds threads and concurrency to the otherwise, single threaded programming language.

That would also be known as inversion of control, a very popular term in programming. The code result becomes dependant on an external factor, namely, the additional features that come with every Javascript runtime. Control of execution is inverted and handed over to an external entity as described above. In

Top comments (0)