When you first encounter the concept of event loop in JS, you might bump into MDN page of such a topic, the same as I do. I felt pumped before but rather disappointed after, for it doesn’t suffice our arduous learning journey of JS. So I dug deeper, trying to understand the real meat behind this topic. If you are just like me who love to explore what’s under the hood, this article is for you.
First of all, event loop mostly likely concerns queue: what message is sent first and what’s next etc. Here’s an example of queue:
try {console.log("This should be the first message");}catch(e) {console.log("This message will never be run");}finally {console.log("This should come last");}\\ =>This should be the first message\\ =>This should come last
The example given lines up a queue synchronously , which can fulfill most of simple daily chores. But what if we need to run asynchronous code, for instance, creating a promise
, or maybe setting a timeout?
Before we jump into the code, let me introduce some important theories about queuing: microtask and macrotask queue. As the name suggests, one is for bigger task while another for smaller task. For example, setTimeout
and setInterval
are typical macrotask methods , while promise
and async function
are considered microtask bound, and one macro may contain many micros. Most importantly, macrotask is only executed after all microtasks from last macrotask finalized.
setTimeout(()=> console.log("This represents a macrotask"));//One big taskPromise.resolve().then(()=>{console.log("This is the first microtask");console.log("Another micro");})//One smaller task with 2 minitasks// =>This is the first microtask// =>Another micro// =>This represents a macrotask
It’s crystal clear as the pattern illustrated this way. But how about we jumble both sync and async snippets together? What’s the queue right now? I’ll flex another example to explain a real-world scenario before we get to that part.
Assume I’m to implement a dynamic phonebook with React. Something like this:
Usually in this situation, adding a addName
function in App.js
can be described like this:
function addName(e) {e.preventDefault();const target = persons.find((person) => person.name === newName);console.log(target);if (target) {if (window.confirm("It exists. You sure to update the number?")) {const id = target.id;console.log(id);updateNumber(id, target);} else return 0;}const nameObject = {name: newName,number: newNumber};phoneService.create(nameObject).then((returnedObject) => {setPersons(persons.concat(returnedObject));setNewName("");setNewNumber("");}).then(() => {setMessage("New person added!");setTimeout(() => {setMessage(null);}, 5000);});}
I would not throw light on the code in detail at this point since it’s not our focus in this article.
We notice that setTimeout
is invoked after setMessage
hook is complete, and this is logical, because we don’t want to announce we had added a person successfully to the phonebook before we actually added such person. However, there is another more handy way to do this, and what if we hoist setTimeout
even before phoneService
fetch?
setTimeout(() => {setMessage(null);}, 5000);//hoisted setTimeout//rest is the samephoneService.create(nameObject).then((returnedObject) => {setPersons(persons.concat(returnedObject));setNewName(“”);setNewNumber(“”);}).then(() => {setMessage(“New person added!”);});
It still works perfectly! You might wonder what’s going on here. This macrotask always queues last no matter what position it is in a task.
Let’s fulfill the promise I made one minute ago. We make a salad out of first two examples I showed in this article:
try {Promise.resolve().then(()=>{console.log(“This is the first microtask”);console.log(“Another micro”);}).then(()=>setTimeout(()=> console.log(“This represents a macrotask”)));}
catch(e) {console.log(“Is there any error here?”);}finally {console.log(“Is this the last task we run?”);}// =>Is this the last task we run?// =>This is the first microtask// =>Another micro// =>This represents a macrotask
Unsurprisingly, the program follows a pattern as sync first async second, meanwhile in async tasks, micro first macro second.
To recap, for an event loop, sync invocation => microtasks => macrotask
If you wanna know more about more advanced React and JS topics, subscribe me.