DEV Community

Harsh Joshi
Harsh Joshi

Posted on

Concurrency Model in Javascript

Also find this article on Scaler Topics
Image description

The idea of concurrency in a language which runs on a single thread is an interesting one. For those of you who don’t already know, concurrency is simply doing multiple things at the same time. Just like you are doing different stuff in different tabs while you’re also reading this.
Imagine you were bound to do only one thing at a time. No Netflix while you eat! Sure that might help you focus but in your day to day life, but you might feel less productive.
Your resources are getting under utilised. Not just that, you see your contemporaries who are able to multiple things at at the same time. The outline of this article is precisely this. Instead of you, it is javascript we are going to talk about.

💡 Since you are here reading about event loop in JS, this article assumes that you are well aware of the fundamental principles of Javascript. You could start looking at “Link” to read about the fundamentals.

Javascript started as a single threaded synchronous language. A programming language which is supposed to run on a single thread means that this will be able to do only one thing at a time. Doing one thing at a time will require it to follow some order of execution. It is thus called synchronous.

In case of synchronous Javascript code will be executed line by line.
Here’s an example:

let a = 5;
let b = 10;
let c = b/a;

print(c)
Enter fullscreen mode Exit fullscreen mode

Each line is executed one by one. Synchronous code is also called as blocking. This is because line 2 can’t be executed until line 1 is not done executing. You might not realise the “blocking” in this example because operations are usually quick.

let a = 5, b = 10;
let result = thisCalculationTakesTwoMinutes(a,b)
let c = 15, d = 30;
let result2 = d/c;
print(result2)
Enter fullscreen mode Exit fullscreen mode

In the above example, line 2 involves a calculation which takes two minutes to complete. While this call is happening you can’t really go to line 3, even if the computing capacity of your machine has capability to do more at that time.

💡 This is where the concept of concurrency becomes important in Javascript. Given that the language is single threaded unlike other languages like Java which can use multiple threads to achieve concurrency, this curious case of concurrency in JS becomes further interesting.

With the advent of concurrency, javascript took “your child can do more” very seriously. Before we come to talk about concurrency in detail, let us try to define some fundamental concepts that are closely associated.

Single Threaded:
Javascript is a single threaded language. This means all applications ever written using Javascript will use only one thread to run. This idea of single thread is unpopular among the likes of languages like Java, and Python which use Multithreading. (Doing multiple things with more than one thread at once). Single threaded also means that the order of execution is always one at a time.

Non Blocking:

To put it simply, non blocking means that the execution of the program will not be blocked because of some code which takes unusually long to execute. If line one has a slow operation, it should not block line two from executing. Didn’t we just want that?

Asynchronous: Asynchronous means that two or more tasks can happen in parallel. This might sound contradicting to what you read above, but it’s true. In Javascript you can actually do many things at once. We will dive deeper into in the lines to come.

How does code execute in Javascript?

To maintain the order of what’s currently running, Javascript uses a stack while executing the code. You might already be aware that the executions happen inside execution contexts. If not, think of executions contexts as javascript objects that contain the execution of code. Functions have local executions contexts which are generated when the functions are called and everything else is run on the global execution context.

The execution context of the code currently being executed will be on top of the stack. Once it is done execution the execution context is popped out of the stack. This data structure is famously known as the call stack.

Let’s understand with an example:

let sayHello = (name)  { console.log(`Hello ${name}` }
let sayBye = (name) { console.log(`Bye ${name}` } 
let communicate() = (thingToSay) = {console.log(`${thingsToSay`)}

console.log(”Communication Started”)
sayHello("Harsh")
communicate("This is how code is executed in JS")
sayBye("Harsh")
console.log(”Communication Over”)
Enter fullscreen mode Exit fullscreen mode

“Doing things” in Javascript is achieved using a call stack and JS Engine. While JS Engine executes the code, the call stack maintains the order of execution. To begin with, think of call stack as an ordinary stack (the data structure) which holds some objects. The top of the stack is the object representing call in current execution, which upon completion/termination will be popped out and the control will be passed to the next call/ execution context.

Though this is not related here but you should note that sayHello, sayBye and communicate are functions expressions and hence initialisation for them will differ from function declaration.

Here’s what shall happen during execution of the above code.

  1. A global execution context is created and immediately put on the call stack which was earlier empty. Once the context is in the call stack the JS engine begins executing the code.
  2. When the control reaches line 4, it logs “Communication Started” on the console.
  3. On reaching line 4, the control finds a function invocation which creates a local execution context, immediately the execution context of sayHello is put on top of the call stack.
  4. JS Engine will only execute the code within the context of the top element of the stack and hence the code inside sayHello begins executing and “Hello Harsh” is printed on the console.
  5. Since there is no code to further execute The context of sayHello is popped out of the stack and the control moves to the next line.
  6. The control finds another function invocation and the process from 3 to 5 happens for the communicate function. As a result “This is how code is executed in JS” is printed on the console.
  7. When control reaches next line, similar process follows and “Bye Harsh” is printed on the console.
  8. Now, the control is back with the global execution context which was only half done executing. The control starts executing from where it left off and “Communication Over” is logged
  9. Since there is no code left in the global execution context, that is always popped out of the call stack and call stack is empty.

But where is Concurrency?

Concurrency is the ability to do multiple things at once. It comes with asynchronicity which is the soul of the present day Javascript. It bring along another special data structure called a callback queue. Let’s try to understand with a classic example of setTimeout.

Yes, setTimeout introduces most of the developers to the soul(asynchronicity) of Javascript. Whether it has any other use is a debate for another day.

setTimeOut accepts two parameters, a callback function and time in mili seconds.

Back to an example:

console.log("Start")
setTimeOut(function delayThis(){console.log(This will be delayed)}, 5000)
console.log("End")
Enter fullscreen mode Exit fullscreen mode

What do you think will be the out of the above lines of code?

  • Whenever setTimeOut is called, the call stack now contains setTimeOut and waits for the delay to happen before moving on the next line
  • When setTimeout is called, the function call goes to the top of the call stack but is immediately popped. The next line gets executed immediately (without waiting for the delay)

Let’s try to understand the order of execution here. When the code begins executing, the first thing that is logged on the console is “Start”. This is pretty obvious.

When the control comes to second line, setTimeout is called and a callback function is registered for future execution.

💡 If you don’t already know, a callback function is a function which is passed to another function as parameter. Since functions are first class citizens in Javascript they can be passed or returned from another function. You can read more about it on this “blog url”

The control does not wait for the delay to happen. Essentially delay does not mean “a delay in the program” rather it means “a delay in execution of the callback function”. Both are very different things.

Next up the control immediately moves to the third line and the execution context associated with calling of setTimeout is destroyed. When this happens, the callback function is still in the memory just not in that execution context. When it was being registered the callback function is moved to a different memory unit. When the callback is ready for execution (10 seconds have elapsed) for execution, it is moved backed to a data structure, just like all functions calls are moved to call stack for execution.

💡 Note: Not all callbacks have same priority. Some callbacks use a special queue known as the micro task queue/Job queue which has higher priority order than callback queue. It introduces more interesting concepts like starvation in a queue. Job Queue/microtask queue is out of scope of this article but you can read more about it here [URL]

Here’s a more practical example:

console.log("Start")
asyncNetworkCall()
console.log("End")
Enter fullscreen mode Exit fullscreen mode

💡 Assume that the asyncNetworkCall is implemented as an asynchronous function. Which means while the network call is still in function, the control is able to move to line 3 and log something to the console.

Event Loop

Before we start talking about Event Loop. Let’s go back to the building blocks again. There is some javascript code hanging out with your favourite code editor. We know by this point that JS Engine and the call stack are the primary things responsible for running the javascript code. In order for the code to be executed it has to be brought on the call stack for JS Engine to execute it.

Now recall the callback function in the example above. Last we heard of it was when it was in some fancy new memory unit called the callback queue. After 10 seconds have passed, it has to be executed. Event loop is such a mechanism which handles this. This means it manages the lifecycle of a callback function from the callback queue to the call stack.

💡 Whenever you read about event loop next, think of it as a mechanism than anything else.

But How does it work?

Let’s begin with stating the universal truth - “A call stack will have to be empty for event loop to bring anything in it”.

Whenever the call stack is empty, the first entered unit in the callback queue is brought to the call stack by the event loop which was observing both call stack and callback queue.

💡 Did You Notice?
setTimeout of a ‘n’ seconds does not guarantee execution exactly when ‘n’ seconds have passed, rather it guarantees a minimum delay of ‘n’.
This is because after a defined limit, the callback function moves to the callback queue (stating ready for execution) where the event loop is supposed to pick it for actual execution.

A very interesting piece of information about event loop is an event loop does not block. Operations that might potentially require waiting are performed using callbacks and events.

💡 Event loop Visualisation.

Summary:

  • JS is single threaded. It has only one call stack.
  • Execution of code in javascript is always. line by line
  • Code in javascript is executed by JS Engine which uses the call stack to determine the order of execution
  • Event loop is a mechanism through which the ‘calls waiting for execution’ in the callback queue/job queue can be put on the call stack.
  • For any event from callback queue/job queue to come to call stack, the call stack will have to be empty.

Top comments (1)

Collapse
 
vineetjadav73 profile image
vineetjadav

Great blog as the knowledge about Single threaded, non-blocking, asynchronous is much needed because the execution is a cornerstone of JavaScript's concurrency model, enabling efficient handling of multiple tasks without halting program flow. This approach ensures responsiveness by allowing the program to continue processing other tasks while waiting for I/O operations or callbacks. By leveraging event-driven architecture and asynchronous APIs, JavaScript achieves high performance and scalability, making it ideal for modern web applications.
Do check out Best Java Programming Course in India to be expert coder.