Just a post about my exploration of the event loop.
How does JS work? It’s a single threaded runtime. It executes task 1 by 1 form the stack. This is important, whatever is in the stack that will run.
Example1, a classic 1
console.log("START");
setTimeout(()=>{
console.log(`setTimeout`), 0
});
console.log("END");
Output:
START
END
setTimeout
The important thing in JS is that anything asynchronous is put aside for the event loop. 1st all synchronous code is run and then we get to the async stuff. setTimeout
is used to run async code.
Example2, let’s focus only on the synchronous stuff:
function foo() {
console.log("foo start");
setTimeout(()=>{
console.log(`setTimeout2`), 0
});
['elem1', 'elem2'].forEach((elem, idx)=>{
console.log(`Array element ${idx}: ${elem}`)
});
console.log("foo end");
}
console.log("START");
setTimeout(()=>{
console.log(`setTimeout1`), 0
});
foo();
console.log("END");
can you guess the output?
START
foo start
Array element 0: elem1
Array element 1: elem2
foo end
END
setTimeout1
setTimeout2
As you can see the sync code runs 1st and then the async code.
Anything async go back in line to the task queues.
Note: Not all callbacks are asynchronous.
Event Loop
After the synchronous code has run we get the event loop. This loop puts tasks in the stack when the stack is empty and there is a task in the task queues.
Stack empty? yes, task available? yes, then a task goes to the stack and it is run.
How do we know which task to run 1st? It’s based on task queues.
ok, wait, why do we need this kind of a process? Why not run everything synchronously?
JS is a single threaded runtime, if everything is run synchronously then we are blocking everything else, that’s a bad feature when we want to create a server to handle multiple requests, or on a browser when we want to render elements in the browser regularly, detect user input, make HTTP requests etc. It’s better to break the code in small pieces, do multiple things in a loop.
In a browser in 1 event loop we would want to make HTTP requests, refresh the page, handle events, etc. then continue in the next loop.
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
Task Queues
So we talked about how the event loop gets the tasks from task queues.
There are a few different task queues, the have different types of tasks and a few different rules on how they are handled.
The main queues:
- Macro task queue: setTimeout, setInterval, setImmediate
- Promise micro task queue: Promises
- process.nextTick queue: also a micro task queue, has process.nextTick tasks
Important points:
- 1 event loop 1 task from macro queue
- in 1 event loop all the micro tasks are run until they are exhausted, if new micro tasks are generated while processing these they will also be run. Note: we can get stuck in an infinite loop and block all processes if we keep generating microtasks
setTimeout
run task after a given amount of time
setInterval
run a task after a set interval
setImmediate
immediately run the task in the next event loop
Promise
add task after resolving a process
nextTick
run task immediately after completing the current operation in ths same event loop
Note: I will keep improving this article, it’s very barebones and missing quite a bit, if you have any questions ask away!
Please checkout these 1st:
-
[Managing The Event Loop Phases ⭕️ OPTIMIZING NODE JS](https://youtu.be/ol56smloW2Q?si=s-Uosz0WiH3lzNPa) -
[What the heck is the event loop anyway? Philip Roberts JSConf EU](https://youtu.be/8aGhZQkoFbQ?si=t8h7abK8Xx99NPjl) - visualise JS event loop: http://latentflip.com/loupe
- Further Adventures of the Event Loop - Erin Zimmer - JSConf EU 2018: more details on event loops and micro task vs task
- Asynchrony: Under the Hood - Shelley Vohr - JSConf EU
- Process.nextTick and Promise callback
Questions
Example3: can you guess the output here? It’s a big code I know and the explanations haven’t been perfect, I would recommend trying this after going through the resources. But I will say there are a few things which don’t add up in my head?
console.log("START");
let count = 0;
function nextTickFunc() {
count++;
console.log(`nextTick0 loop:${count}`);
}
setImmediate(()=>{
console.log(`setImmediate0 loop:${count}`);
});
setTimeout(()=>{
console.log(`setTimeout0 loop:${count}`), 0
});
loopTracker = process.nextTick(()=>{
nextTickFunc();
});
setInterval(function () {
process.nextTick(()=>{
nextTickFunc();
});
console.log(`setInterval0 loop:${count}`);
if (count > 3) {
clearInterval(this)
}
}, 0);
new Promise((resolve, reject) => {
resolve(`Promise0 loop:${count}`);
}).then(resolve => {
console.log(resolve);
process.nextTick(()=>{
console.log(`nextTick1 loop:${count}`);
new Promise((resolve, reject) => {
resolve(`Promise3 loop:${count}`);
}).then(resolve => {
console.log(resolve);
});
process.nextTick(()=>{
console.log(`nextTick2 loop:${count}`);
process.nextTick(()=>{
console.log(`nextTick3 loop:${count}`);
});
});
});
new Promise((resolve, reject) => {
resolve(`Promise1 loop:${count}`);
}).then(resolve => {
console.log(resolve);
new Promise((resolve, reject) => {
resolve(`Promise2 loop:${count}`);
}).then(resolve => {
console.log(resolve);
});
});
});
console.log("END");
output:
START
END
nextTick0 loop:1
Promise0 loop:0
Promise1 loop:1
Promise2 loop:1
nextTick1 loop:1
nextTick2 loop:1
nextTick3 loop:1
Promise3 loop:1
setTimeout0 loop:1
setInterval0 loop:1
nextTick0 loop:2
setImmediate0 loop:2
setInterval0 loop:2
nextTick0 loop:3
setInterval0 loop:3
nextTick0 loop:4
setInterval0 loop:4
nextTick0 loop:5
- in the 4th line of the output, why is the count 0
Promise0 loop:0
? - Why do we have the following
Promise0 loop:0 Promise1 loop:1
and not the following?
Promise0 loop:0 nextTick1 loop:1
- why is it
setImmediate0 loop:2
and notsetImmediate0 loop:1
? - why did we stop at
nextTick0 loop:5
and notnextTick0 loop:4
?
I have an idea about 2, 3, 4 but I don’t know about 1, if you have any opinions please share! I’d love to discuss.
PS: What can I use to make quick animations for illustration purposes? I would also like something to simulate on the frontend with? It would be nice to make it interactive. It should be something easy to use, quick to get the job done. Currently exploring P5.js