The Event Loop
Understanding asynchronicity in JavaScript requires understanding one fundamental concept: what will the JS engine execute next? This is a very simplified overview of how to answer this question, more formally known as the Event Loop.
JavaScript is (for the most part) single threaded, so if everything in JavaScript was synchronous, The JS engine would execute every statement one by one as they appear in the source code, wait for the execution to finish, and go to the next line.
However that would be incredibly limiting when it comes to web development. To solve this problem, some APIs that the browser/node.js provide are asynchronous, which basically means that they don't execute when the JS engine first runs into them. Instead they get put in a queue, to be executed once all the synchronous statements have finished. Let's consider:
function printHello() {
console.log("Hello");
}
setTimeout(printHello, 0);
console.log("Me first!");
Because setTimeout
is told to execute printHello
at 0 milliseconds, one could reason that the output should be:
Hello
Me first!
But in fact the the output is
Me first!
Hello
This is because setTimeout is an asynchronous API (a callback function), so its execution gets placed in the "task queue". Anything in the task queue is only executed after all synchronous code has already run.
Note: console.log
is in fact itself an asynchronous function but I'm glossing over that detail for the sake of simplicity and clear demonstration of the concept.
Promises
Promises, introduced in ES6, add one additional queue to the mix. Consider:
function display(data){console.log(data)}
function printHello(){console.log("Hello");}
function blockForLong(){
const arr = [];
for (let i = 0; i < 3_000_000_000; i++>){
arr.push(i)
}
}
setTimeout(printHello, 0);
const futureData = fetch('https://twitter.com/AmeriRyan/status/1291935897076641792')
futureData.then(display)
blockForLong()
console.log("Me first!");
This code won't run correctly as this is not exactly how fetch() works, but for the sake of simplicity, let's assume that fetch
is a function that takes a URL as a string and returns a Promise. blockForLong
is a function that doesn't do anything important for our purposes but is a synchronous function that takes a long time to execute. We first call setTimeout
that runs printHello
at 0 milliseconds. Then we handle the Promise and pass it to a function display
that just prints it to console. Then we execute blockForLong
and finally we execute console.log
. Can you guess what gets printed first?
First, all synchronous code is executed. That means blockForLong
is called first, and then Me first!
is printed to console. Promises get placed in a queue called the "micro task queue", which has priority over the "task queue" where callback functions are placed. So even though setTimeout
appears first in the source code, we first call the display
function with the returned data, and only call the printHello
function last.
So, the Event Loop in JavaScript, in a nutshell, is:
- Synchronous code
- Anything in the micro task queue (Promises)
- Anything in the task queue (callback functions)
If you can follow the execution order in the this example, you should be able to solve all the upcoming exercises (perhaps with a bit of help from MDN).
In the next section, we'll practice 10 exercises that should help us master asynchronicity as well as introduce us to Promises.
Top comments (1)
Great post! I like the way you approached demonstrating this concept 👍