Javascript Execution and How It Works In Depth
I have been diving into how Javascript works under the hood recently and I have to say it’s really been helpful with my mental model of how Javascript is actually working and executing code. It also helps explain certain behaviours that were some what mysterious before.
In this article I wanted to share what I have learnt as I think this knowledge is not widely known in the Javascript community. As I have gained experience in Software Development I have noticed that in any programming language many of the details are hidden away. Whilst this makes the code less verbose it also hides away important details which unless you dig for it, you can remain totally ignorant of this.
7 Parts of Javascript Execution
We’re going to go over this Javascript code and see how it executes to explain some of the details of Javascript execution:
setTimeout(() => {
console.log('Am I first?');
}, 0);
function printData(data) {
console.log('data');
}
// Assume this fetch request takes 200ms
const futureData = fetch('https://jsonplaceholder.typicode.com/todos/1');
futureData.then(printData);
blockFor400ms();
console.log('I am first!');
With this code we will see in what order the console.logs run. Through this code we will discover all about the following 7 elements of Javascript Execution:
- Global Execution Context
- Microtask Queue
- Macrotask/Callback Queue
- Event Loop
- Web Browser APIs
- Thread Of Execution
- The Call Stack
If you understand all these things and how they work together, then your mental model of how Javascript works will be much better. It will also make your ability to read Javascript code much more accurate.
Macrotask/Callback Queue & Web Browser APIs
One thing to make clear right away is when you are seeing JS code, some of the code will be executed in the Javascript engine and some will be done elsewhere. (Code that is computational like arithmetic will be executed by the Javascript engine). An example of this is setTimeout()
. This is a ‘facade’ function. It isn’t part of the core Javascript language. It actually needs to interact with the environment — the Web Browser APIs (to be precise) — in order to execute.
When I first realised this it blew me away. So when we’re looking at some JS code it’s not obvious where each bit of code will be executed? What?! I thought all JS code will be executed by the JS engine?
setTimeout
tells the browser to set up a timer (which is the number of milliseconds entered as the second argument) and when that timer is finished, to execute the callback function that was passed as the first argument.
Well… this isn’t 100% accurate. When setTimeout
finishes its timer, the function, which is the first argument, gets put into the Macrotask/callback Queue. Here it waits until it’s time for execution comes around (which is the job of the Event Loop but we will get to that in a little bit).
Thread Of Execution
Javascript is single threaded, which means it goes through the code and executes line by line. It doesn’t skip and jump around the code. It doesn’t do random stuff. It methodically goes through the code and executes line by line. It is nice and predictable.
However, just because it does this, don’t get the impression that in the above code the console.logs will run in the order that they appear. Unfortunately for people new to Javascript, you have to discover that there is quite a bit of stuff going on under the hood.
We’ve seen setTimeout
. While that was happening, Javascript’s thread continued on to the next line which was the function.
function printData(data) {
console.log('data');
}
When Javascript sees this, it will allocate a piece of memory (in the global memory) to this which will consist of the function name and then the whole function with all the code inside. It doesn’t get run at this point. It is just storing it in memory so that it can be used later.
To call a function and have the code in it execute, you have to use parentheses
()
after the function name.
So far, nothing has been logged to the console.
Promises
Next up in the code are these two lines:
// Assume this fetch request takes 200ms
const futureData = fetch('https://jsonplaceholder.typicode.com/todos/1');
futureData.then(printData);
blockFor400ms();
I have put these together as it will make more sense in the explanation. I don’t want to go too deep into promises here so I will definitely be taking some liberties with my explanation but I will endeavour to include the important parts.
The first line of this code declares a constant which is named futureData
and this will be the returned value of the right hand side’s event which in this case is a fetch
function.
fetch
is a facade function so it doesn’t create an execution context in the usual sense like a standard function is JS does. It is a function that masks a bunch of JS work and a bunch of Web Browser work.
When this fetch
function is called, like it is in the code example, it does two things:
- Starts a background process in the Web Browser to use XHR (XMLHttpRequest) under the hood to send a request to the url provided as an argument to the fetch function. By default it does a GET request.
- Creates and returns a Promise Object in Javascript land. This will be filled in later on when the background task in the Web Browser has completed.
So at this point we have a constant futureData
in Javascript memory which is pointing towards this Promise Object. We are now waiting for the Response from the fetch
function.
This Promise Object has some special properties on it — value
and onFulfilment
(there are other properties also but we are not interested in those for this example). When the XHR request has successfully retrieved the data (we assume here it will always be successful) it is passed back to Javascript land where the value
field can be updated in memory with this returned data. This then triggers the functions sitting in the onFulfilment
property to run with the data in the value
property to be used as those functions input.
We then get to this line:
futureData.then(printData);
This then()
name is not a good name in terms of describing what it is actually doing in reality. It should be replaced with a name like storeFunctionWeWantToAutoTriggerOnValuePropertyBeingUpdated
but obviously this isn’t as catchy!
This is the job of the then()
method. Taking the function it is given and putting it into the onFulfilment
array in the Promise Object so when the value
property is populated there is a function in the onFulfilment
property and it can be triggered.
We have the blockFor400ms
function here to allow for the fetch request to take place and return some data. This gives time for the printData
function to get into the Microtask queue (this is the queue that functions from Promises resolving get put into).
This means in the code example we started with we are now at the last line of code:
console.log('I am first!');
and this is run straight away. So ‘I am first’ is the first thing to print to the console.
Event Loop & Call Stack
The obvious next question is: What runs next?
This is where we can take a little look into the Event loop, the Macrotask queue, the Microtask queue and the Call Stack.
As I haven’t clarified yet the Call Stack is there to track the execution of functions. When a function is called it is ‘pushed’ to the stack and when it completes it is ‘popped’ off.
The Event Loops role is to manage the execution of asynchronous tasks in Javascript. It checks the Call Stack and then executes tasks from the Macrotask queue and the Microtask queue. (It actually checks the Microtask queue first and then the Macrotask queue.)
setTimeout(() => {
console.log('Am I first?');
}, 0);
function printData(data) {
console.log('data');
}
// Assume this fetch request takes 200ms
const futureData = fetch('https://jsonplaceholder.typicode.com/todos/1');
futureData.then(printData);
blockFor400ms();
console.log('I am first!');
In our code example then we have already console.logged ‘I am first’.
Next up we now have the printData
function in the Microtask queue ready to run (thanks to this function: blockFor400ms
) and the function from the setTimeout
function in the Macrotask queue ready to run. Which goes first?
The event loop will check the Microtask queue first so the printData
function is run:
console.log('data');
This is followed by the setTimeout
function running:
console.log('Am I first?');
Conclusion
I have realised over time that gaining knowledge and understanding about how the systems work in whatever you are doing is the most valuable type of knowledge.
This is because it gives you perspective and context on a much wider angle than anything else you might learn. This has an impact in your everyday work and decisions. It’s also knowledge that lasts in a world of ever changing frameworks.
I knew about all these terms before but then I watched this Frontend masters course and it really helped me gain a deeper understanding of the Javascript and how it executes. I strongly encourage you to give it a watch.
Subscribe to My Weekly Updates!
Enjoyed This Post?
If you found this blog post helpful, why not stay updated with my latest content? Subscribe to receive email notifications every time I publish. New posts go live every Monday at 8:30 AM Central European Time.
What You’ll Get
By subscribing, you’ll get:
- Exciting Discoveries: Be the first to know about the latest tools and libraries.
- How-To Guides: Step-by-step articles to enhance your development skills.
- Opinion Pieces: Thought-provoking insights into the world of frontend development.
Join Our Community
I live in the vibrant city of Prague, Czech Republic, with my family. My blog is more than just articles; it’s a community of like-minded developers who share a love for innovation and learning.
About me
I’m a passionate Frontend Developer specialising in React and TypeScript. My professional journey revolves around exploring and mastering new tools and libraries within the JavaScript ecosystem.
Check out the original blog post on my blog.