5.8 JS Runtime
Overview
The JS runtime serves as the central handler for executing JavaScript code within Deno. Instead of being a standalone JavaScript engine, it acts as a bridge between Deno and the v8 engine. The JS runtime leverages a component called "rusty_v8" to establish communication with the v8 engine. This communication facilitates various operations, ranging from loading and initializing modules to executing code and managing the well-known event loop. A substantial portion of code in the JS runtime is dedicated to interfacing with v8.
In the context of any user-level program, the JS runtime offers a "future" that reaches completion under three primary conditions:
Encountering an error during execution.
Successful evaluation of all modules.
Completion of all pending operations.
Should none of the above scenarios occur, the future remains in a continuous polling state within the event loop. To illustrate, consider our "hello world" example where the future concludes swiftly due to the absence of waiting operations.
It's important to note that the JS runtime handles a multitude of tasks, contributing to its inherent complexity. Here, we'll provide a high-level overview of the JS runtime's functionality. As we delve into the execution of scripts, we'll gain a more comprehensive understanding of its inner workings. Undoubtedly, the JS runtime serves as a pivotal hub where numerous significant actions take place, making it a critical component within Deno's ecosystem.
Initialization of JS runtime
The process of initializing the JavaScript runtime occurs through several sequential steps. Let's take a closer look at these steps to gain a better understanding.
v8_init
The initial step involves setting up v8, the core engine of Deno.
Begin by generating a fresh default platform within the v8 framework.
Proceed to initialize this platform.
Finally, kickstart the v8 engine itself.
Global context
In the world of v8, we encounter the concept of "contexts." These contexts pertain to scripts or modules and play an important role. Yet, alongside them, there exists what we call the "global context." This special global context comes into existence as soon as v8 is initialized for operation. It serves a vital purpose in various aspects, including module loading, instantiation, and more.
The process of establishing this global context unfolds through a series of steps, with the initial stage involving the creation of what are known as "isolates." These isolates are integral components originating from v8 itself. If you're curious to delve deeper into the specifics of isolates, you can find valuable insights on the website v8.dev. Essentially, isolates are set up and initialized at this point in our discussion. This marks the foundation of the broader context in which code and modules operate within Deno.
State
The JsRuntime requires a complex state that it manages using v8's slots. This state is composed of several different elements, including:
Deno operates while keeping track of numerous states to ensure its proper functioning. As we delve further into running our code, we will uncover some of these states. This concludes our discussion on the initialization process. Now, let's explore how the JsRuntime carries out the execution of programs. For the time being, we will maintain a higher-level perspective to grasp the overall process.
Execute
In Deno, the JsRuntime offers a handy tool called "execute_script()" for carrying out script execution within the V8 engine. It's crucial to keep in mind that Deno serves as a runtime environment, not merely a JavaScript execution engine. The actual running of scripts takes place within the V8 engine.
The purpose of the "execute_script()" function is to handle the execution of conventional JavaScript code, which refers to code without ES modules. If your code follows the traditional structure, this function is your go-to. However, if you're dealing with code organized into ES modules, fret not! Deno provides distinct functions tailored for such module-based JavaScript.
An interesting point to note is that the "execute_script()" function operates within the global context. This means that any code executed using this function will interact with the broader environment and variables. This global context approach might impact how your code behaves and communicates with other parts of your program.
In essence, Deno's JsRuntime simplifies script execution through the "execute_script()" function, emphasizing compatibility with traditional JavaScript while accommodating module-based code through separate functions. Understanding the distinction between these execution methods can significantly enhance your effectiveness when working with Deno.
The execution of a script in Deno involves several significant steps that work together to make things happen:
Obtaining the Global Context: At the outset, Deno fetches the global context, which provides the foundational environment for your script's execution.
Initializing the Script or File: Next, Deno takes the necessary actions to set up and prepare the script or file for execution, ensuring that everything is in place.
Setting Up v8's TryCatch Error Handler: Deno allocates v8's TryCatch error handler, a tool that helps manage and capture errors during the execution process, enhancing the reliability of the script.
Compiling the Script: The script is then compiled, which involves transforming the human-readable code into a form that the computer can understand and execute.
Running the Script: With all the groundwork laid, Deno finally runs the compiled script, bringing it to life and allowing it to perform its intended tasks.
This sequence of steps showcases the intricate process Deno follows to take your code from its initial form to actual execution. Each step plays a crucial role in ensuring that your script runs smoothly and successfully carries out its desired operations.
ES Modules
One crucial role of the JS runtime is to facilitate and manage ES modules, which have distinct requirements compared to traditional JS code. The JS runtime accomplishes this through a range of significant functions.
Within the realm of the JS runtime, numerous functions are dedicated to aiding ES modules. Let's delve into a brief overview of a selection of these pivotal functions:
Function | Use |
new_es_module |
|
instantiate_module |
|
mod_evaluate |
|
dyn_mod_evaluate |
|
load_main_module |
|
load_side_module |
|
One crucial role of the JS runtime is to facilitate and manage ES modules, which have distinct requirements compared to traditional JS code. The JS runtime accomplishes this through a range of significant functions.
Within the realm of the JS runtime, numerous functions are dedicated to aiding ES modules. Let's delve into a brief overview of a selection of these pivotal functions:
Event loop
The JavaScript runtime also operates the renowned event loop, which has gained prominence within the realm of JavaScript, particularly through Node.js. This event loop can be considered as a kind of enchanting cycle that remains active until a program has completed its tasks.
In essence, the event loop represents a path to the future, persistently operating as long as there are tasks yet to be accomplished. Its fundamental code structure can be depicted as follows:
Understanding the event loop might seem a bit tricky without delving into the actual code. We will get a clearer picture of the event loop as we walk through the evaluation process of the main module in the hello world program. But for now, let's grasp a high-level overview of what the event loop accomplishes.
The event loop mechanism is best understood by examining the poll_event_loop function. This function operates asynchronously and continues running until certain conditions are met:
An error is encountered, or
All tasks are completed, implying that there's nothing left to be done. This involves checking for various scenarios:
No pending operations are waiting to be executed.
No dynamic imports are in the queue, waiting for their turn.
All pending dynamic imports have been successfully evaluated.
All modules in the evaluation queue have been processed.
No active inspector sessions are ongoing.
By adhering to these conditions, the event loop ensures the orderly execution of tasks within the Deno runtime environment. As we proceed, we will dive into the intricacies of these concepts, shedding light on the orchestration of asynchronous operations and module evaluations, ultimately resulting in a well-functioning Deno application.
If there's nothing to be done, the event loop comes to a halt. In our illustration, the event loop would conclude swiftly. However, in real-world scenarios, this loop would hardly ever reach its end. Take, for instance, a web server—it perpetually remains poised to receive new connections. Applications in production would generally only terminate when an error surfaces.
Let's delve into some higher-level tasks that the poll event loop undertakes:
Polling Pending Operations: It scans for operations that are pending, waiting for them to become available.
Verifying Responses from Asynchronous Operations: The loop checks the responses from asynchronous operations, making sure they are ready.
Draining Macrotasks: It processes larger-scale tasks, often known as "macrotasks," from the queue.
Handling Promise Exceptions: The loop takes a look at promise-based operations to catch and handle any exceptions that might have occurred.
Prepping Dynamic Module Imports: It readies the environment for importing dynamic modules, which are modules that can be loaded on demand.
Polling Dynamic Imports: The loop keeps an eye out for dynamically imported modules, ensuring they're fetched when necessary.
Evaluating Dynamic Module Imports: It assesses and evaluates the dynamically imported modules.
Checking Promise Exceptions Again: The loop revisits promise-based operations to double-check for exceptions.
Assessing Pending Top-Level Modules: It evaluates and processes any pending top-level modules that are waiting to be executed.
Verifying for Exceptions: The loop examines the code for any potential exceptions before proceeding.
As we continue through our program in this and the subsequent chapters, we will gradually uncover different facets of the event loop. Without a relevant context or an illustrative example, comprehending the intricacies of the event loop can be quite challenging.
Next steps
That concludes the discussion on the JS runtime. Up until now, we've delved into the process of initialization, which primarily focused on getting things ready. Let's transition to the exciting phase of action—executing the user-level code in our simple "hello world" program. Our attention now shifts to the upcoming segment, which is all about carrying out the main module's execution. In the following passages, we will explore the intricacies of loading and running our code, unraveling the steps that bring our program to life.
Last updated