Understanding JavaScript Runtime Environment
Call Stack, Stack Overflow Error, Web APIs, Callback Queue and Event Loop.
In becoming a better JavaScript developer, you need to understand the concept of how JavaScript executes scripts under the hood. In this article, we will better understand how the JavaScript runtime environment works.
The Javascript runtime environment allows Javascript code to be run and consists of the Javascript engine, the Web APIs, a callback queue, and the event loop. The web browser has an inbuilt runtime environment, as in the case of the Chrome browser, called the V8 engine. This enables JavaScript codes to be run on the web browser.
However, in order to run JavaScript code outside of the browser, the JavaScript runtime environment needs to be made available. For example, Node.js is a JavaScript runtime environment that allows you to run JavaScript codes outside of the web browser.
CALL STACK
The JavaScript engine uses a call stack to manage script execution.
According to MDN, a call stack is a mechanism for an interpreter (such as the Javascript interpreter) to keep track of functions in a script that call multiple functions, i.e., what function is currently being run and what functions are being called from within that function.
When you execute a script, the JavaScript engine would create a Global Execution Context and push it to the top of the call stack.
Global Execution Context is the default execution context in which JS code start its execution when the file first loads in the browser.
Whenever a function is called, the JavaScript engine creates a Function Execution Context for the function, pushes it on top of the call stack, and starts executing the function.
A functional execution context is the context created by the JS engine whenever it finds any function call. Each function has its own execution context.
If a function calls another function, the JavaScript engine creates a new Function Execution Context for the function that is being called and pushes it on top of the call stack.
function multiply(a, b){
return a * b;
}
function square(c){
return multiply(c,c);
}
square(8)
From the image above, when the script is executed, a global execution context is created main()
.The first function, which is square()
is executed and pushed as the first item on the stack. Subsequently, the function multiply()
is executed and pushed to the top of the stack.
A function is immediately popped out of the stack when a script gets to a return statement, . Therefore, the multiply
is popped first, followed by the square
function, and then the main()
.
As soon as the call stack is empty, the script stops execution .
The call stack uses the LIFO principle (Last In, First Out).
The last function in the stack, multiply()
is the first function that popped out.
STACK OVERFLOW ERROR
When a function is being invoked recursively, i.e., a function keeps calling itself without any point of exit, it returns a stack overflow error.
This happens because a call stack has a limited size and when this size is exceeded, it throws an error. RangeError: Maximum call stack size exceeded
The maximum call stack size limit ranges from 10 to 50 thousand calls. For the chrome browser, the size limit is 16,000.
function baz(){
baz();
}
baz()
WEB APIs
Javascript is a single threaded language, which means that it runs synchronously and handles tasks one at a time. JavaScript has a single call stack, due to its single-threadedness.
In this section, we will look at how asynchronous functions operate and how they are placed in the order of execution in JavaScript.
While JavaScript as a language is synchronous, it is possible to run tasks asynchronously, and this is possible through the APIs provided by the browser.
Application Programming Interfaces (APIs) are constructs made available in programming languages to permit developers to form complex functionality more easily. They abstract more complex code far from you, providing some easier syntax to use in its place.
The APIs in client-side JavaScript are divided into two categories:
- Browser/Web APIs
- Third-party APIs
Browser APIs: These are built into the browser that sits on top of the JavaScript language and permit you to implement functionality more easily.
Third-party APIs: These are built into third-party platforms (e.g., Twitter, Facebook) that allow you to use a number of those platforms's functionality in your own sites (for example, displaying your latest Tweets on your web page).
For instance, when we make an API request or image load to the server, the interpreter would not be able to do anything else until a response is gotten from the server.
This can make our application slow and unusable . With the web APIs, the execution is handled, so this would not block the call stack, and other tasks can be run while we wait for the response.
const a = () => console.log('I');
const b = () => setTimeout(() => console.log('love'), 1000);
const c = () => console.log('JavaScript');
a();
b();
c();
From our initial knowledge of the call stack, the result should be printed as I
, love
and then JavaScript
because the functions have a console.log
statement and the script should be executed after each console.log
or return
statement.
However, the setTimeout
function is an asynchronous function, and it is being executed concurrently while the next statement is being executed.
Steps of execution
function a
is invoked and executed first. The resultI
is output (Step 1).function b
is invoked and triggers the execution of the web API (Step 2), and aftersetTimeout
finishes its execution, it adds the callback to the callback queue. In the next section, we will learn what the callback queue is.function c
is invoked and executed last, but it is output second because while thesetTimeout
is being executed asynchronously, the JS interpreter continues to this task and the resultJavaScript
is output (Step 3).
CALLBACK QUEUE AND EVENT LOOP
When an asynchronous function like setTimeout
gets called, after being executed by the Web APIs, it gets added to the callback queue (Step 4).
A callback queue uses the First-In, First-Out (FIFO) principle.
The functions added to the callback queue are processed in that order. When the event loop in Javascript is fired, it first checks the call stack to see if it is non-empty.
An event loop monitors the call stack and the callback queue. If the call stack is empty, the event loop will take the first event from the queue and will push it to the call stack, effectively running it.
In our code instance used above, the event loop checks the call stack to be empty after function a
and function c
have been executed and takes function b
from the callback queue and pushes it to the call stack where it gets executed (Step 5). The script is said to be completed when the call stack and the callback queue are empty.
CONCLUSION
I hope this article was able to help you understand some concepts happening behind the scenes of your JavaScript code. Please leave a comment if you have any questions or feedback.
Some resources I found helpful while researching on this topic: