5.9 Run main module
Last updated
Last updated
Up to this point, we have successfully completed the initialization process for different components within Deno. Our main goal in initiating Deno was to run a basic "hello world" program. However, up until now, Deno has not yet engaged with our actual code—except for organizing the module specifier. The present moment marks the juncture where we get deep into the steps of arranging, loading, and ultimately running our code.
Let's take a look at the code responsible for executing the main module:
Recall that the main worker was already created for the main module:
The awaited moment has arrived with the introduction of the Run() function in Deno. This remarkable function serves as the catalyst for executing our code. Although it might seem simple with only two steps, the impact of these steps is profound and far-reaching.
Let's explore the complexities of these two fundamental steps:
Preload module -- The first step entails the preloading of the module. This involves a meticulous process of recursively fetching not only the module itself, but also its interconnected dependencies. The web of connections is delicately woven as each dependent module is brought into the fold. -- Once the fetching is complete, the loaded modules are seamlessly integrated into the v8 engine, where their contents are prepared for execution.
Instantiate the main module -- The second step involves the instantiation of the main module. This module, representing the heart of our code, is meticulously prepared for execution. -- The module undergoes a rigorous evaluation process, where its contents are scrutinized and prepared for interpretation. Within the v8 engine, the module's instructions are comprehended and organized. -- With everything in place, the module is set to be executed within the v8 engine. The culmination of this execution triggers a crucial waiting period, during which the module's evaluation result is anticipated.
Run event loop -- The completion of these preparatory steps paves the way for the commencement of the event loop. This is the dynamic core of Deno's execution, where various asynchronous tasks are managed and orchestrated.
No matter how intricate the application might be, the beginning always takes place within the main module. This process occurs in a recursive manner, starting from the core, which is the main module itself. Since this operation involves asynchronous actions, the 'await' keyword is used at its conclusion.
Naturally, the execution of the 'run' function takes place within the main worker. This is a component of the central processing thread. Now, let's see how the 'run' function is implemented:
Module loading is a crucial aspect of Deno, with all related code running asynchronously. We'll dive into a detailed examination of module loading, uncovering its inner mechanics as they relate to our code, specifically the main module. Our journey begins with the initial phase, "preload", which is embedded in the execute_main_module_possibly_with_npm function.
The preload module is used to load and instantiate any ES module. This isn't limited to the main module. It is applicable to any ES module.
The code of preload_module is very simple:
The function "preload_main_module" essentially delegates the task to the JavaScript runtime's "load_main_module" function. The process of loading the main module takes place in a sequence of four steps:
Loading the Main Module: Initially, the main module is loaded into the runtime environment.
Recursive Dependency Loading: The process then involves poking into the main module and subsequently loading all the dependencies it requires. This step is carried out recursively, meaning that dependencies of dependencies are also loaded as needed.
Main Module Instantiation: After the dependencies are loaded, the main module is instantiated. This means that the code within the main module is executed and any initializations or setups are performed.
Dependency Resolution: Once the main module is instantiated, the process moves on to resolve any dependencies. This involves making sure that all the required components are available and connected properly.
The outcome of the "load_module" function is an identification number for the module. This identification number needs to be immediately used when evaluating the module. This sequential procedure ensures that the main module and its dependencies are loaded, prepared, and connected appropriately within the Deno runtime environment.
Evaluation is essentially the same as execution. This is the term used by V8, the JavaScript engine, and it's also the term Deno adopts. When we talk about evaluation, we're essentially asking V8 to carry out the instructions contained in the code. In Deno, it's the way we prompt V8 to execute the module that has been created.
However, it's important to note that the outcome of this evaluation doesn't show up right away. Instead, it appears as something called a "future." This future represents the result that we expect from the evaluation process. It's like a placeholder that will eventually be filled with the outcome of the evaluation. Only when the module evaluation is completely done, this future gets resolved – meaning it gets filled in with the actual result we were waiting for. This process ensures that we can keep track of the progress and completion of the evaluation while allowing us to work with the results effectively once they're ready.
The job of event loop is to keep track of the evaluation result.
The process of evaluation in Deno involves asynchronous calls that reach their conclusions through various pathways. These pathways are as follows:
Completion due to Tasks Done:
When all tasks within the main module have been executed.
This includes resolving any pending operations (ops).
Also, this covers the completion of dynamic import processing.
Unhandled Exception:
If an unhandled exception occurs during the execution.
When there are no pending tasks remaining and no dynamic imports to process, the program's evaluation comes to a conclusion, signifying that the program has finished its intended operations. In the context of our uncomplicated example, it's anticipated that the evaluation process will promptly come to an end since there are no asynchronous operations or never-ending event listeners in the program. We will dig deeper into the intricacies of how this evaluation process operates a little later in the text, gaining a better understanding of its functioning.
--
Now that we have a general understanding of the process, let's explore the critical stages of loading and evaluation in more depth. First, we'll examine the intricacies of the loading process. Next, we'll analyze the loading procedure of a simple "hello world" program, which lacks imports and consists of a single main module. This straightforward example allows us to understand the fundamental principles more easily.
In the next chapter, we'll explore a program with multiple imports, which adds complexity to the loading mechanism. This will provide a more comprehensive understanding of Deno's capabilities.