5.9 Execute_module

So far we've finished the initialization of various parts of Deno. Although the purpose to start Deno is to run our hello world program, so far Deno hasn't even touched our code, well except for preparing the module specifier. Now is the time to prepare, load, and execute our code.
Here is the code that executes the main module:


Execute_module() is what we were waiting for. This is the function that'll take our code and execute it. There are just two steps taken by the execute module. Two steps sound very less, but they do a lot of work.
Here are two steps:
  • Preload module
    • Recursively fetch module and it's dependencies
    • Load module(s) in v8
    • Instantiate the main module
  • Evaluate module
    • Evaluate or execute in v8
    • Wait for evaluation result
Execute_modules takes the module specifier of the main module. Regardless of the complexity of the application, the start is always from the main module. It goes recursively from the root which is the main module. This is an async call, so await is there at the end.
The execute module function is executed on the main worker. This is part of the main thread. Let's see the implementation of execute_module:
pub async fn execute_module(
&mut self,
module_specifier: &ModuleSpecifier,
) -> Result<(), AnyError> {
let id = self.preload_module(module_specifier).await?;
Module loading is a critical part of Deno. All the module loading code is completely async. We'll look into module loading in detail and see how it works for our code i.e. the main module. Let's start with the first step which is preload.

Preload module

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:
pub async fn preload_module(
&mut self,
module_specifier: &ModuleSpecifier,
) -> Result<ModuleId, AnyError> {
self.js_runtime.load_module(module_specifier, None).await
Preload_module simply defers the work to JS runtime's load_module.
The loading of the main module happens in four steps:
  • Load the main module
  • Recursively go inside and load all the dependencies
  • Instantiate the main module
  • Resolve all the dependencies
The return from load_module is a module id that needs to be passed to the module evaluation immediately.

Evaluate module

Evaluation is equivalent to execution. V8 calls it evaluation, so does the code present in Deno. Evaluation requests v8 to execute the instantiated module.
The result of the module evaluation isn't available immediately. The result comes as a future that gets resolved when module evaluation finishes.

Receive evaluation result

Evaluation is an asynchronous call that gets concluded in the following ways:
  • There is nothing left to do in the main module
    • No pending ops
    • No dynamic import processing
  • There is an unhandled exception
If there is nothing left to do, the program is done. In our simple example, it's expected that the evaluation would immediately finish as there are no async ops or never-ending listeners in the program. We'll see a bit later on how evaluation works.
Let's go through the important steps of loading and evaluation in detail. First, we'll go through how loading is done. In the next section, we'll see how a simple program like hello world gets loaded. This one is simple because there are no imports. There is just a single main module. Without dependencies, the loading process gets easier. That'll help us to understand the core concepts. In the next chapter, we'll go over a program that has some imports.