Understanding the Event Loop
Asked Answered
N

4

154

I am thinking about it and this is what I came up with:

Let's see this code below:

console.clear();
console.log("a");
setTimeout(function(){console.log("b");},1000);
console.log("c");
setTimeout(function(){console.log("d");},0);

A request comes in, and JS engine starts executing the code above step by step. The first two calls are sync calls. But when it comes to setTimeout method, it becomes an async execution. But JS immediately returns from it and continue executing, which is called Non-Blocking or Async. And it continues working on other etc.

The results of this execution is the following:

a c d b

So basically the second setTimeout got finished first and its callback function gets executed earlier than the first one and that makes sense.

We are talking about single-threaded application here. JS Engine keeps executing this and unless it finishes the first request, it won't go to second one. But the good thing is that it won't wait for blocking operations like setTimeout to resolve so it will be faster because it accepts the new incoming requests.

But my questions arise around the following items:

#1: If we are talking about a single-threaded application, then what mechanism processes setTimeouts while the JS engine accepts more requests and executes them? How does the single thread continue working on other requests? What works on setTimeout while other requests keep coming in and get executed.

#2: If these setTimeout functions get executed behind the scenes while more requests are coming in and being executed, what carries out the async executions behind the scenes? What is this thing that we talk about called the EventLoop?

#3: But shouldn't the whole method be put in the EventLoop so that the whole thing gets executed and the callback method gets called? This is what I understand when talking about callback functions:

function downloadFile(filePath, callback)
{
   blah.downloadFile(filePath);
   callback();
}

But in this case, how does the JS Engine know if it is an async function so that it can put the callback in the EventLoop? Perhaps something like the async keyword in C# or some sort of an attribute which indicates the method JS Engine will take on is an async method and should be treated accordingly.

#4: But an article says quite contrary to what I was guessing on how things might be working:

The Event Loop is a queue of callback functions. When an async function executes, the callback function is pushed into the queue. The JavaScript engine doesn't start processing the event loop until the code after an async function has executed.

#5: And there is this image here which might be helpful but the first explanation in the image is saying exactly the same thing mentioned in question number 4:

enter image description here

So my question here is to get some clarifications about the items listed above?

Normalcy answered 6/2, 2014 at 15:54 Comment(14)
Threads aren't the right metaphor to handle those problems. Think events.Alanson
@dystroy : A code sample to illustrate that event metaphor in JS would be nice to see.Normalcy
I don't see what's exactly your question here.Alanson
@dystroy: My question here is to get some clarifications about the items listed above?Normalcy
Node isn't single threaded but that doesn't matter for you (apart the fact that it manages to get other things done while your user code executes). Only one callback at most in your user code is executed at one time.Alanson
@dystroy, node is single threaded. Don't mistake it with libuv which isn't.Pianoforte
@Pianoforte No it's not. Read the answer if you're confused about that.Alanson
@dystroy, I'm not confused. Try to grep -R pthread node/lib node/src, and compare it with grep -R pthread node/deps/uv. Node.js own source code doesn't create threads.Pianoforte
node is no single threadedAlanson
See also: this, this, and this.Pathan
explained clearly here : altitudelabs.com/blog/what-is-the-javascript-event-loopBascule
@DenysSéguret You need to understand which parts of node.js is multithreaded though. Node.js by default (ignoring worker_process which you can launch yourself) only runs 5 threads: the main thread, a thread for disk I/O, a thread for cryptography, a thread for DNS requests and a thread for zip compression. All network I/O are done in the main thread so from a practical point of view node is single threadedFarsighted
@Normalcy - I've written lots of answers detailing how asynchronous/non-blocking code work, primarily form the point of view of javascript but also applies to other languages. Here are links to some of them: #29884025 , #62165875 , #49102377Farsighted
.. more links: #28962198 , #62971155 , #61262554 , #39246247Farsighted
S
100

1: If we are talking about a single-threaded application, then what processes setTimeouts while JS engine accepts more requests and executes them? Isn't that single thread will continue working on other requests? Then who is going to keep working on setTimeout while other requests keep coming and get executed.

There's only 1 thread in the node process that will actually execute your program's JavaScript. However, within node itself, there are actually several threads handling operation of the event loop mechanism, and this includes a pool of IO threads and a handful of others. The key is the number of these threads does not correspond to the number of concurrent connections being handled like they would in a thread-per-connection concurrency model.

Now about "executing setTimeouts", when you invoke setTimeout, all node does is basically update a data structure of functions to be executed at a time in the future. It basically has a bunch of queues of stuff that needs doing and every "tick" of the event loop it selects one, removes it from the queue, and runs it.

A key thing to understand is that node relies on the OS for most of the heavy lifting. So incoming network requests are actually tracked by the OS itself and when node is ready to handle one it just uses a system call to ask the OS for a network request with data ready to be processed. So much of the IO "work" node does is either "Hey OS, got a network connection with data ready to read?" or "Hey OS, any of my outstanding filesystem calls have data ready?". Based upon its internal algorithm and event loop engine design, node will select one "tick" of JavaScript to execute, run it, then repeat the process all over again. That's what is meant by the event loop. Node is basically at all times determining "what's the next little bit of JavaScript I should run?", then running it. This factors in which IO the OS has completed, and things that have been queued up in JavaScript via calls to setTimeout or process.nextTick.

2: If these setTimeout will get executed behind the scenes while more requests are coming and in and being executed, the thing carry out the async executions behind the scenes is that the one we are talking about EventLoop?

No JavaScript gets executed behind the scenes. All the JavaScript in your program runs front and center, one at a time. What happens behind the scenes is the OS handles IO and node waits for that to be ready and node manages its queue of javascript waiting to execute.

3: How can JS Engine know if it is an async function so that it can put it in the EventLoop?

There is a fixed set of functions in node core that are async because they make system calls and node knows which these are because they have to call the OS or C++. Basically all network and filesystem IO as well as child process interactions will be asynchronous and the ONLY way JavaScript can get node to run something asynchronously is by invoking one of the async functions provided by the node core library. Even if you are using an npm package that defines it's own API, in order to yield the event loop, eventually that npm package's code will call one of node core's async functions and that's when node knows the tick is complete and it can start the event loop algorithm again.

4 The Event Loop is a queue of callback functions. When an async function executes, the callback function is pushed into the queue. The JavaScript engine doesn't start processing the event loop until the code after an async function has executed.

Yes, this is true, but it's misleading. The key thing is the normal pattern is:

//Let's say this code is running in tick 1
fs.readFile("/home/barney/colors.txt", function (error, data) {
  //The code inside this callback function will absolutely NOT run in tick 1
  //It will run in some tick >= 2
});
//This code will absolutely also run in tick 1
//HOWEVER, typically there's not much else to do here,
//so at some point soon after queueing up some async IO, this tick
//will have nothing useful to do so it will just end because the IO result
//is necessary before anything useful can be done

So yes, you could totally block the event loop by just counting Fibonacci numbers synchronously all in memory all in the same tick, and yes that would totally freeze up your program. It's cooperative concurrency. Every tick of JavaScript must yield the event loop within some reasonable amount of time or the overall architecture fails.

Salimeter answered 6/2, 2014 at 16:34 Comment(10)
Lets say I have a queue that will take the server 1 minute to execute, and the first thing was some async function that finished after 10 seconds. Will it go to the end of the queue or will it push itself into the line the instant it is ready?Freddafreddi
Generally it will go to the end of the queue, but the semantics of process.nextTick vs setTimeout vs setImmediate are subtly different, although you shouldn't really have to care. I have a blog post called setTimeout and friends that goes into more detail.Salimeter
Can you please elaborate? Let us say I have two callbacks and the first one has a changeColor method with execution time of 10mS and a setTimeout of 1 minute and the second has a changeBackground method with execution time of 50ms with a setTimeout of 10 seconds. I feel the changeBackground with be in the Queue first and the changeColor will be next. After that the Event loop picks the methods synchronously. Am I right?Inside
@Inside it's too confusing for everyone to discuss code when written in paragraphs of English. Just post a new question with a code snippet so people can answer based on code instead of a description of code, which leaves a lot of ambiguity.Salimeter
youtube.com/watch?v=QyUFheng6J0&spfreload=5 this is another good explanation of JavaScript EngineCrudity
@PeterLyons: So the main JavaScript thread "dies" each time after executing all synchronous code? And the event loop then re-spawns the main thread whenever a new callback from the task queue is to be executed?Zofiazoha
@maverick Yes that's conceptually right just be careful with your wording. The tick of javascript completes I think is a better word to use than "dies" and since in threading library context the word "spawn" often means constructing a new thread, that also doesn't happen, the main javascript thread is just given another tick worth of synchronous javascript to execute, but the thread itself is long-lived (as threads are generally expensive to create).Salimeter
@PeterLyons: Interesting! But I don't really understand what a "tick" means on a technical level?Zofiazoha
I think of "tick" meaning a single instance of the event loop running a section of javascript code. I suspect some others think of it as a single iteration of the event loop code (which might include no-ops like when there's no JS ready to run yet).Salimeter
@Peter Lyons, In your answer you have mentioned that there are other node threads. Can you explain what these threads do and why Node even included them?Fatuitous
U
12

Don't think the host process to be single-threaded, they are not. What is single-threaded is the portion of the host process that execute your javascript code.

Except for background workers, but these complicate the scenario...

So, all your js code run in the same thread, and there's no possibility that you get two different portions of your js code to run concurrently (so, you get not concurrency nigthmare to manage).

The js code that is executing is the last code that the host process picked up from the event loop. In your code you can basically do two things: run synchronous instructions, and schedule functions to be executed in future, when some events happens.

Here is my mental representation (beware: it's just that, I don't know the browser implementation details!) of your example code:

console.clear();                                   //exec sync
console.log("a");                                  //exec sync
setTimeout(                //schedule inAWhile to be executed at now +1 s 
    function inAWhile(){
        console.log("b");
    },1000);    
console.log("c");                                  //exec sync
setTimeout(
    function justNow(){          //schedule justNow to be executed just now
        console.log("d");
},0);       

While your code is running, another thread in the host process keep track of all system events that are occurring (clicks on UI, files read, networks packets received etc.)

When your code completes, it is removed from the event loop, and the host process return to checking it, to see if there are more code to run. The event loop contains two event handler more: one to be executed now (the justNow function), and another within a second (the inAWhile function).

The host process now try to match all events happened to see if there handlers registered for them. It found that the event that justNow is waiting for has happened, so it start to run its code. When justNow function exit, it check the event loop another time, searhcing for handlers on events. Supposing that 1 s has passed, it run the inAWhile function, and so on....

Unroof answered 6/2, 2014 at 16:33 Comment(1)
The setTimeout is implemented in the main thread though. So there is nothing in your example that requires a separate thread. In fact, in browsers only tabs are implemented in multiple threads. In a single tab all processes including making multiple parallel network connections, waiting for mouse clicks, setTimeout, animations etc. are done in the same threadFarsighted
O
4

The Event Loop has one simple job - to monitor the Call Stack, the Callback Queue and Micro task queue. If the Call Stack is empty, the Event Loop will take the first event from the micro task queue then from the callback queue and will push it to the Call Stack, which effectively runs it. Such an iteration is called a tick in the Event Loop.

As most developers know, that Javascript is single threaded, means two statements in javascript can not be executed in parallel which is correct. Execution happens line by line, which means each javascript statements are synchronous and blocking. But there is a way to run your code asynchronously, if you use setTimeout() function, a Web API given by the browser, which makes sure that your code executes after specified time (in millisecond).

Example:

console.log("Start");

setTimeout(function cbT(){
console.log("Set time out");
},5000);

fetch("http://developerstips.com/").then(function cbF(){
console.log("Call back from developerstips");
});

// Millions of line code
// for example it will take 10000 millisecond to execute

console.log("End");

setTimeout takes a callback function as first parameter, and time in millisecond as second parameter. After the execution of above statement in browser console it will print

Start
End
Call back from developerstips
Set time out

Note: Your asynchronous code runs after all the synchronous code is done executing.

Understand How the code execution line by line

JS engine execute the 1st line and will print "Start" in console

In the 2nd line it sees the setTimeout function named cbT, and JS engine pushes the cbT function to callBack queue.

After this the pointer will directly jump to line no.7 and there it will see promise and JS engine push the cbF function to microtask queue.

Then it will execute Millions of line code and end it will print "End"

After the main thread end of execution the event loop will first check the micro task queue and then call back queue. In our case it takes cbF function from the micro task queue and pushes it into the call stack then it will pick cbT funcion from the call back queue and push into the call stack.

enter image description here

Outrageous answered 13/5, 2021 at 20:11 Comment(6)
Event loop initially doesn't take the task from the micro queue. In fact, it takes the first task from the task queue and if it's empty then it performs a check on micro queue. Either way, oldest task in the task queue gets performed first. For ex, If you have a setTimeout of 0 and a Promise... the Promise callback will be executed first because setTimeout of 0 means min of 4ms and it only enqueues as a task if the time is elapsed. Here's the model: html.spec.whatwg.org/multipage/…Soembawa
Hi @nullhook, microtask queue will always get the 1st priority can you please have a look on developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/… line There are two key differences........Outrageous
Have you tried reading the event loop model?Soembawa
Have you tried to set the timeout delay to 0ms (for the cbT callback)? Wouldn't this prove the point that the microtask queue has a higher priority than the callback queue? When you use a longer timeout, I believe it is just that the setTimeout API puts the callback in the callback queue after this big timeout and by then the event loop has gone through the microtask queue already. Makes sense? Maybe the proof is here (eliminates the specifics around fetch implementation, caching etc.): developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/…Leelah
@Outrageous how does addEventListener() work regarding the available queues? say if you add an event listener callback for a 'click' when the click itself happens or when you simulate a dispatchEvent of a clickDesign
Hi @FernandoGabrieli If you added any addEventListener () to an event, then that should be called directly (by any other event) or indirectly (within some other event)Outrageous
S
-2

JavaScript is high-level, single-threaded language, interpreted language. This means that it needs an interpreter which converts the JS code to a machine code. interpreter means engine. V8 engines for chrome and webkit for safari. Every engine contains memory, call stack, event loop, timer, web API, events, etc.

Event loop: microtasks and macrotasks

The event loop concept is very simple. There’s an endless loop, where the JavaScript engine waits for tasks, executes them and then sleeps, waiting for more tasks

Tasks are set – the engine handles them – then waits for more tasks (while sleeping and consuming close to zero CPU). It may happen that a task comes while the engine is busy, then it’s enqueued. The tasks form a queue, so-called “macrotask queue

Microtasks come solely from our code. They are usually created by promises: an execution of .then/catch/finally handler becomes a microtask. Microtasks are used “under the cover” of await as well, as it’s another form of promise handling. Immediately after every macrotask, the engine executes all tasks from microtask queue, prior to running any other macrotasks or rendering or anything else.

enter image description here

Subaqueous answered 11/7, 2021 at 18:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.