6.7 Evaluate module

Overview

Evaluation of a module is the same as running a program. Deno makes a call to v8's module evaluate function. V8 gets into action and evaluates the module as fast as it can.
As mentioned earlier, the evaluation procedure happens only on the main module. This is like running the main program in C/C++/Java code.

Evaluation of hello world v2

Here is the relevant evaluation code from the worker:
pub async fn execute_module(
&mut self,
module_specifier: &ModuleSpecifier,
) -> Result<(), AnyError> {
let id = self.preload_module(module_specifier).await?;
self.wait_for_inspector_session();
self.js_runtime.mod_evaluate(id).await
}
The output of preload_module is instantiated modules. The next step is to evaluate the root or main module. Preload module returns the id of the root module which would get passed to the module evaluation function.
To recall, mod_evaluate performs two actions:
  • Calls mod_evaluate_inner
    • This calls v8's evaluate function
    • Creates a global promise which would either get fulfilled or rejected sometime in future
  • Asynchronously waits in poll_event_loop till the global promise moves from pending
pub async fn mod_evaluate(&mut self, id: ModuleId) -> Result<(), AnyError> {
let mut receiver = self.mod_evaluate_inner(id);
poll_fn(|cx| {
if let Poll::Ready(maybe_result) = receiver.poll_next_unpin(cx) {
debug!("received module evaluate {:#?}", maybe_result);
let result = maybe_result.unwrap_or(Ok(()));
return Poll::Ready(result);
}
let _r = self.poll_event_loop(cx)?;
Poll::Pending
})
.await
}
In the previous chapter, there were no async ops in the basic hello world program. So, the poll event loop inside mod_evaluate got finished immediately as the global promise moved to a fulfilled state. Once the module evaluation was completed, the program moved on to window load, event loop, window unload, and then got finished.
However, in this case, there are async and sync ops:
  • getMachineId uses a sync op
  • doze uses async setTimeout to sleep for 1 second
  • env.get is a sync op
  • truncate is an async op
Though there is a mixture of ops, they won't take time as these are very few and simple ops. The only exception is setTimeout which would delay the module evaluation because it has to sleep for 1 second.
Once all the ops are completed, the program will print:
1
One
TEST_ENV_VAL
As mentioned earlier, we would go over the last two ops to show how ops are implemented:
  • Sync op
    • env.get
  • Async op
    • truncate
Combined, these are enough to understand how the bridge is crossed, how ops are dispatched, and how results are processed.
In the next section, let's see how a sync op works.

Event loop

Once the main module has been submitted for evaluation, the event loop would be entered. This time the event loop would run till none of these are remaining:
  • No pending ops
  • No pending dynamic imports
  • No dynamic imports pending evaluation
  • No top-level modules pending evaluation
By the time the main module evaluation is completed, all the ops would also be done. The remaining steps i.e. window load, the event loop, and window unload would be the same as the hello world program.