loading...

Mastering Hard Parts of JavaScript: Asynchronicity I

internettradie profile image Ryan Ameri ・3 min read

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:

  1. Synchronous code
  2. Anything in the micro task queue (Promises)
  3. 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.

Posted on by:

internettradie profile

Ryan Ameri

@internettradie

Re-discovering frontend development. Professional translator & interpreter. Amateur powerlifter. BLM. He/him/his

Discussion

pic
Editor guide
 

Great post! I like the way you approached demonstrating this concept 👍