Stage 2 Draft / April 24, 2024

Deferred Imports Evaluation

10 Ordinary and Exotic Objects Behaviours

10.4 Built-in Exotic Object Internal Methods and Slots

This specification defines several kinds of built-in exotic objects. These objects generally behave similar to ordinary objects except for a few specific situations. The following exotic objects use the ordinary object internal methods except where it is explicitly specified otherwise below:

10.4.6 Module Namespace Exotic Objects

A module namespace exotic object is an exotic object that exposes the bindings exported from an ECMAScript Module (See 16.2.3). There is a one-to-one correspondence between the String-keyed own properties of a module namespace exotic object and the binding names exported by the Module. The exported bindings include any bindings that are indirectly exported using export * export items. Each String-valued own property key is the StringValue of the corresponding exported binding name. These are the only String-keyed properties of a module namespace exotic object. Each such property has the attributes { [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: false }. Module namespace exotic objects are not extensible.

An object is a module namespace exotic object if its [[GetPrototypeOf]], [[SetPrototypeOf]], [[IsExtensible]], [[PreventExtensions]], [[GetOwnProperty]], [[DefineOwnProperty]], [[HasProperty]], [[Get]], [[Set]], [[Delete]], and [[OwnPropertyKeys]] internal methods use the definitions in this section, and its other essential internal methods use the definitions found in 10.1. These methods are installed by ModuleNamespaceCreate.

Module namespace exotic objects have the internal slots defined in Table 1.

Table 1: Internal Slots of Module Namespace Exotic Objects
Internal Slot Type Description
[[Module]] a Module Record The Module Record whose exports this namespace exposes.
[[Exports]] a List of Strings A List whose elements are the String values of the exported names exposed as own properties of this object. The list is ordered as if an Array of those String values had been sorted using %Array.prototype.sort% using undefined as comparefn.

10.4.6.8 [[Get]] ( P, Receiver )

The [[Get]] internal method of a module namespace exotic object O takes arguments P (a property key) and Receiver (an ECMAScript language value) and returns either a normal completion containing an ECMAScript language value or a throw completion. It performs the following steps when called:

  1. If P is a Symbol, then
    1. Return ! OrdinaryGet(O, P, Receiver).
  2. Let m be O.[[Module]].
  3. If m is not a Cyclic Module Record, then
    1. Perform ? EvaluateSync(m).
  4. Otherwise if m.[[Status]] is not evaluated and ReadyForSyncExecution(m) is true, then:
    1. Perform ? EvaluateSync(m).
  5. Let exports be O.[[Exports]].
  6. If exports does not contain P, return undefined.
  7. Let m be O.[[Module]].
  8. Let binding be m.ResolveExport(P).
  9. Assert: binding is a ResolvedBinding Record.
  10. Let targetModule be binding.[[Module]].
  11. Assert: targetModule is not undefined.
  12. If binding.[[BindingName]] is namespace, then
    1. Return GetModuleNamespace(targetModule).
  13. Let targetEnv be targetModule.[[Environment]].
  14. If targetEnv is empty, throw a ReferenceError exception.
  15. Return ? targetEnv.GetBindingValue(binding.[[BindingName]], true).
Note 1

ResolveExport is side-effect free. Each time this operation is called with a specific exportName, resolveSet pair as arguments it must return the same result. An implementation might choose to pre-compute or cache the ResolveExport results for the [[Exports]] of each module namespace exotic object.

Note 2
ReadyForSyncExecution can return false when this [[Get]] operation on a namespace coming from a deferred import is triggered during the evaluation of one of its dependencies, either synchronously or asynchronously. In that case, the module cannot be fully evaluated and some of its exports will not be initialized yet.

10.4.6.8.1 ReadyForSyncExecution ( module [ , seen ] )

The abstract operation ReadyForSyncExecution takes argument module (a Cyclic Module Record) and optional argument seen (a List of Module Records) and returns a Boolean. It performs the following steps when called:

  1. If seen is not provided, let seen be a new empty List.
  2. If seen contains module, return true.
  3. Append module to seen.
  4. If module.[[Status]] is evaluated, return true.
  5. If module.[[Status]] is evaluating or evaluating-async, return false.
  6. Assert: module.[[Status]] is linked.
  7. If module.[[HasTLA]] is true, return false.
  8. For each ModuleRequest Record required of module.[[RequestedModules]], do
    1. Let requiredModule be GetImportedModule(module, required.[[Specifer]]).
    2. If ReadyForSyncExecution(requiredModule, seen) is false, then
      1. Return false.
  9. Return true.

13 ECMAScript Language: Expressions

13.3 Left-Hand-Side Expressions

Syntax

ImportCall[Yield, Await] : import ( AssignmentExpression[+In, ?Yield, ?Await] ) import . defer ( AssignmentExpression[+In, ?Yield, ?Await] )

13.3.10 Import Calls

Editor's Note
The changes to this section are intendend to mirror the changes proposed by the Source Phase Imports proposal, but adapted to the defer phase.

13.3.10.1 Runtime Semantics: Evaluation

ImportCall : import ( AssignmentExpression )
  1. Return ? EvaluateImportCall(AssignmentExpression, evaluation).
  2. Let referrer be GetActiveScriptOrModule().
  3. If referrer is null, set referrer to the current Realm Record.
  4. Let argRef be ? Evaluation of AssignmentExpression.
  5. Let specifier be ? GetValue(argRef).
  6. Let promiseCapability be ! NewPromiseCapability(%Promise%).
  7. Let specifierString be Completion(ToString(specifier)).
  8. IfAbruptRejectPromise(specifierString, promiseCapability).
  9. Perform HostLoadImportedModule(referrer, specifierString, empty, promiseCapability).
  10. Return promiseCapability.[[Promise]].
ImportCall : import . defer ( AssignmentExpression[+In, ?Yield, ?Await] )
  1. Return ? EvaluateImportCall(AssignmentExpression, defer).

13.3.10.2 EvaluateImportCall ( specifierExpression, phase )

The abstract operation EvaluateImportCall takes arguments specifierExpression (a ParseNode) and phase (defer or evaluation) and returns either a normal completion containing a Promise or a throw completion.

Editor's Note
The diff presented for this algorithm is relative to the original algorithm for the Evaluation of ImportCall : import ( AssignmentExpression ).

It performs the following steps when called:

  1. Let referrer be GetActiveScriptOrModule().
  2. If referrer is null, set referrer to the current Realm Record.
  3. Let specifierRef be ? Evaluation of AssignmentExpressionspecifierExpression.
  4. Let specifier be ? GetValue(specifierRef).
  5. Let promiseCapability be ! NewPromiseCapability(%Promise%).
  6. Let specifierString be Completion(ToString(specifier)).
  7. IfAbruptRejectPromise(specifierString, promiseCapability).
  8. Let payload be a new DynamicImportState Record { [[PromiseCapability]]: promiseCapability, [[Phase]]: phase }.
  9. Perform HostLoadImportedModule(referrer, specifierString, empty, promiseCapabilitypayload).
  10. Return promiseCapability.[[Promise]].

A DynamicImportState Record is a Record that contains information about a dynamic import call. It's used to continue loading after a call to HostLoadImportedModule. Each DynamicImportState Record has the fields defined in Table 2:

Table 2: DynamicImportState Record Fields
Field Name Value Type Meaning
[[PromiseCapability]] a PromiseCapability Record The promise to resolve when the loading process finishes.
[[Phase]] defer or evaluation The target import phase

13.3.10.2.1 ContinueDynamicImport ( promiseCapability, payload, moduleCompletion )

The abstract operation ContinueDynamicImport takes arguments promiseCapability (a PromiseCapability Record), payload (a DynamicImportState Record), and moduleCompletion (either a normal completion containing a Module Record or a throw completion) and returns unused. It completes the process of a dynamic import originally started by an import() call, resolving or rejecting the promise returned by that call as appropriate. It performs the following steps when called:

  1. Let promiseCapability be payload.[[PromiseCapability]].
  2. Let phase be payload.[[Phase]].
  3. If moduleCompletion is an abrupt completion, then
    1. Perform ! Call(promiseCapability.[[Reject]], undefined, « moduleCompletion.[[Value]] »).
    2. Return UNUSED.
  4. Let module be moduleCompletion.[[Value]].
  5. Let loadPromise be module.LoadRequestedModules().
  6. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures promiseCapability and performs the following steps when called:
    1. Perform ! Call(promiseCapability.[[Reject]], undefined, « reason »).
    2. Return UNUSED.
  7. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
  8. Let linkAndEvaluateClosure be a new Abstract Closure with no parameters that captures module, promiseCapability, phase and onRejected and performs the following steps when called:
    1. Let link be Completion(module.Link()).
    2. If link is an abrupt completion, then
      1. Perform ! Call(promiseCapability.[[Reject]], undefined, « link.[[Value]] »).
      2. Return UNUSED.
    3. Let evaluatePromise be module.Evaluate()EMPTY.
    4. Let fulfilledClosure be a new Abstract Closure with no parameters that captures module and promiseCapability and performs the following steps when called:
      1. Let namespace be GetModuleNamespace(module).
      2. Perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace »).
      3. Return UNUSED.
    5. If phase is defer, then
      1. Let evaluationList be a new empty List.
      2. Perform GatherAsynchronousTransitiveDependencies(module, evaluationList).
      3. If evaluationList is empty, then
        1. Perform fulfilledClosure().
        2. Return UNUSED.
      4. Let asyncDepsEvaluationPromises be a new empty List.
      5. For each Module Record dep of evaluationList, append dep.Evaluate() to asyncDepsEvaluationPromises.
      6. Let iterator be CreateListIteratorRecord(asyncDepsEvaluationPromises).
      7. Let pc be ! NewPromiseCapability(%Promise%).
      8. Set evaluatePromise to ! PerformPromiseAll(iterator, %Promise%, pc, %Promise.resolve%).
    6. Else,
      1. Assert: phase is evaluation.
      2. Set evaluatePromise to module.Evaluate().
    7. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, "", 0, « »).
    8. Perform PerformPromiseThen(evaluatePromise, onFulfilled, onRejected).
    9. Return UNUSED.
  9. Let linkAndEvaluate be CreateBuiltinFunction(linkAndEvaluateClosure, "", 0, « »).
  10. Perform PerformPromiseThen(loadPromise, linkAndEvaluate, onRejected).
  11. Return UNUSED.

16 ECMAScript Language: Scripts and Modules

16.2 Modules

Syntax

Module : ModuleBodyopt ModuleBody : ModuleItemList ModuleItemList : ModuleItem ModuleItemList ModuleItem ModuleItem : ImportDeclaration ExportDeclaration StatementListItem[~Yield, +Await, ~Return] ModuleExportName : IdentifierName StringLiteral

16.2.1 Module Semantics

16.2.1.1 ModuleRequest Records

A ModuleRequest Record represents the request to import a module up to a given phase. It consists of the following fields:

Table 3: ModuleRequest Record fields
Field Name Value Type Meaning
[[Specifier]] String The module specifier
[[Phase]] defer or evaluation The target import phase
Editor's Note
In general, this proposal replaces places where module specifiers are passed around with ModuleRequest Records. For example, several syntax-directed operations, such as ModuleRequests produce Lists of ModuleRequest Records rather than Lists of Strings which are interpreted as module specifiers. Some algorithms like ImportEntries and ImportEntriesForModule pass around ModuleRequest Records rather than Strings, in a way which doesn't require any particular textual change. Additionally, record fields in Cyclic Module Records and Source Text Module Records which contained Lists of Strings are replaced by Lists of ModuleRequest Records, as indicated above.

16.2.1.3 Static Semantics: ModuleRequests

The syntax-directed operation ModuleRequests takes no arguments and returns a List of StringsModuleRequest Records. It is defined piecewise over the following productions:

Module : [empty]
  1. Return a new empty List.
ModuleItemList : ModuleItem
  1. Return ModuleRequests of ModuleItem.
ModuleItemList : ModuleItemList ModuleItem
  1. Let moduleNamesrequests be ModuleRequests of ModuleItemList.
  2. Let additionalNamesadditionalRequests be ModuleRequests of ModuleItem.
  3. For each String name of additionalNames, do
  4. For each ModuleRequest Record mr of additionalRequests, do
    1. Let found be false.
    2. For each ModuleRequest Record mr2 of requests, do
      1. If mr.[[Specifer]] is mr2.[[Specifer]] and mr.[[Phase]] is mr2.[[Phase]], then
        1. Assert: found is false.
        2. Set found to true.
    3. If moduleNames does not contain name, then
    4. If found is false, then
      1. Append namemr to moduleNamesrequests.
  5. Return moduleNamesrequests.
ModuleItem : StatementListItem
  1. Return a new empty List.
ImportDeclaration : import ImportClause FromClause ;
  1. Return ModuleRequests of FromClause.
  2. Let specifier be SV of FromClause.
  3. Return a List whose sole element is the ModuleRequest Record { [[Specifer]]: specifier, [[Phase]]: evaluation }.
ImportDeclaration : import defer ImportClause FromClause ;
  1. Let specifier be SV of FromClause.
  2. Return a List whose sole element is the ModuleRequest Record { [[Specifer]]: specifier, [[Phase]]: defer }.
ModuleSpecifier : StringLiteral
  1. Return a List whose sole element is the SV of StringLiteral.
ExportDeclaration : export ExportFromClause FromClause ;
  1. Return ModuleRequests of FromClause.
  2. Let specifier be SV of FromClause.
  3. Return a List whose sole element is the ModuleRequest Record { [[Specifer]]: specifier, [[Phase]]: evaluation }.
ExportDeclaration : export NamedExports ; export VariableStatement export Declaration export default HoistableDeclaration export default ClassDeclaration export default AssignmentExpression ;
  1. Return a new empty List.

16.2.1.5 Cyclic Module Records

A Cyclic Module Record is used to represent information about a module that can participate in dependency cycles with other modules that are subclasses of the Cyclic Module Record type. Module Records that are not subclasses of the Cyclic Module Record type must not participate in dependency cycles with Source Text Module Records.

In addition to the fields defined in Table 40 Cyclic Module Records have the additional fields listed in Table 4

Table 4: Additional Fields of Cyclic Module Records
Field Name Value Type Meaning
[[Status]] new, unlinked, linking, linked, evaluating, evaluating-async, or evaluated Initially new. Transitions to unlinked, linking, linked, evaluating, possibly evaluating-async, evaluated (in that order) as the module progresses throughout its lifecycle. evaluating-async indicates this module is queued to execute on completion of its asynchronous dependencies or it is a module whose [[HasTLA]] field is true that has been executed and is pending top-level completion.
[[EvaluationError]] a throw completion or empty A throw completion representing the exception that occurred during evaluation. undefined if no exception occurred or if [[Status]] is not evaluated.
[[DFSIndex]] an integer or empty Auxiliary field used during Link and Evaluate only. If [[Status]] is either linking or evaluating, this non-negative number records the point at which the module was first visited during the depth-first traversal of the dependency graph.
[[DFSAncestorIndex]] an integer or empty Auxiliary field used during Link and Evaluate only. If [[Status]] is either linking or evaluating, this is either the module's own [[DFSIndex]] or that of an "earlier" module in the same strongly connected component.
[[RequestedModules]] a List of StringsModuleRequest Records A List of all the ModuleSpecifier strings used by the module represented by this record to request the importation of a module, along with their maximum specified import phase (defer or evaluation). The List is in source text occurrence order.
[[LoadedModules]] a List of Records with fields [[Specifier]] (a String) and [[Module]] (a Module Record) A map from the specifier strings used by the module represented by this record to request the importation of a module to the resolved Module Record. The list does not contain two different Records with the same [[Specifier]].
[[CycleRoot]] a Cyclic Module Record or empty The first visited module of the cycle, the root DFS ancestor of the strongly connected component. For a module not in a cycle, this would be the module itself. Once Evaluate has completed, a module's [[DFSAncestorIndex]] is the [[DFSIndex]] of its [[CycleRoot]].
[[HasTLA]] a Boolean Whether this module is individually asynchronous (for example, if it's a Source Text Module Record containing a top-level await). Having an asynchronous dependency does not mean this field is true. This field must not change after the module is parsed.
[[AsyncEvaluation]] a Boolean Whether this module is either itself asynchronous or has an asynchronous dependency. Note: The order in which this field is set is used to order queued executions, see 16.2.1.5.3.4.
[[TopLevelCapability]] a PromiseCapability Record or empty If this module is the [[CycleRoot]] of some cycle, and Evaluate() was called on some module in that cycle, this field contains the PromiseCapability Record for that entire evaluation. It is used to settle the Promise object that is returned from the Evaluate() abstract method. This field will be empty for any dependencies of that module, unless a top-level Evaluate() has been initiated for some of those dependencies.
[[AsyncParentModules]] a List of Cyclic Module Records If this module or a dependency has [[HasTLA]] true, and execution is in progress, this tracks the parent importers of this module for the top-level execution job. These parent modules will not start executing before this module has successfully completed execution.
[[PendingAsyncDependencies]] an integer or empty If this module has any asynchronous dependencies, this tracks the number of asynchronous dependency modules remaining to execute for this module. A module with asynchronous dependencies will be executed when this field reaches 0 and there are no execution errors.

In addition to the methods defined in Table 41 Cyclic Module Records have the additional methods listed in Table 5

Table 5: Additional Abstract Methods of Cyclic Module Records
Method Purpose
InitializeEnvironment() Initialize the Environment Record of the module, including resolving all imported bindings, and create the module's execution context.
ExecuteModule( [ promiseCapability ] ) Evaluate the module's code within its execution context. If this module has true in [[HasTLA]], then a PromiseCapability Record is passed as an argument, and the method is expected to resolve or reject the given capability. In this case, the method must not throw an exception, but instead reject the PromiseCapability Record if necessary.

A GraphLoadingState Record is a Record that contains information about the loading process of a module graph. It's used to continue loading after a call to HostLoadImportedModule. Each GraphLoadingState Record has the fields defined in Table 6:

Table 6: GraphLoadingState Record Fields
Field Name Value Type Meaning
[[PromiseCapability]] a PromiseCapability Record The promise to resolve when the loading process finishes.
[[IsLoading]] a Boolean It is true if the loading process has not finished yet, neither successfully nor with an error.
[[PendingModulesCount]] a non-negative integer It tracks the number of pending HostLoadImportedModule calls.
[[Visited]] a List of Cyclic Module Records It is a list of the Cyclic Module Records that have been already loaded by the current loading process, to avoid infinite loops with circular dependencies.
[[HostDefined]] anything (default value is empty) It contains host-defined data to pass from the LoadRequestedModules caller to HostLoadImportedModule.

16.2.1.5.1 LoadRequestedModules ( [ hostDefined ] )

The LoadRequestedModules concrete method of a Cyclic Module Record module takes optional argument hostDefined (anything) and returns a Promise. It populates the [[LoadedModules]] of all the Module Records in the dependency graph of module (most of the work is done by the auxiliary function InnerModuleLoading). It takes an optional hostDefined parameter that is passed to the HostLoadImportedModule hook. It performs the following steps when called:

  1. If hostDefined is not present, let hostDefined be empty.
  2. Let pc be ! NewPromiseCapability(%Promise%).
  3. Let state be the GraphLoadingState Record { [[IsLoading]]: true, [[PendingModulesCount]]: 1, [[Visited]]: « », [[PromiseCapability]]: pc, [[HostDefined]]: hostDefined }.
  4. Perform InnerModuleLoading(state, module).
  5. Return pc.[[Promise]].
Note
The hostDefined parameter can be used to pass additional information necessary to fetch the imported modules. It is used, for example, by HTML to set the correct fetch destination for <link rel="preload" as="..."> tags. import() expressions never set the hostDefined parameter.

16.2.1.5.1.1 InnerModuleLoading ( state, module )

The abstract operation InnerModuleLoading takes arguments state (a GraphLoadingState Record) and module (a Module Record) and returns unused. It is used by LoadRequestedModules to recursively perform the actual loading process for module's dependency graph. It performs the following steps when called:

  1. Assert: state.[[IsLoading]] is true.
  2. If module is a Cyclic Module Record, module.[[Status]] is new, and state.[[Visited]] does not contain module, then
    1. Append module to state.[[Visited]].
    2. Let requestedModulesCount be the number of elements in module.[[RequestedModules]].
    3. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] + requestedModulesCount.
    4. For each String required of module.[[RequestedModules]], do
    5. For each ModuleRequest Record required of module.[[RequestedModules]], do
      1. Let specifier be required.[[Specifier]].
      2. If module.[[LoadedModules]] contains a Record whose [[Specifier]] is requiredspecifier, then
        1. Let record be that Record.
        2. Perform InnerModuleLoading(state, record.[[Module]]).
      3. Else,
        1. Perform HostLoadImportedModule(module, requiredspecifier, state.[[HostDefined]], state).
        2. NOTE: HostLoadImportedModule will call FinishLoadingImportedModule, which re-enters the graph loading process through ContinueModuleLoading.
      4. If state.[[IsLoading]] is false, return unused.
  3. Assert: state.[[PendingModulesCount]] ≥ 1.
  4. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] - 1.
  5. If state.[[PendingModulesCount]] = 0, then
    1. Set state.[[IsLoading]] to false.
    2. For each Cyclic Module Record loaded of state.[[Visited]], do
      1. If loaded.[[Status]] is new, set loaded.[[Status]] to unlinked.
    3. Perform ! Call(state.[[PromiseCapability]].[[Resolve]], undefined, « undefined »).
  6. Return unused.

16.2.1.5.1.2 ContinueModuleLoading ( state, moduleCompletion )

The abstract operation ContinueModuleLoading takes arguments state (a GraphLoadingState Record) and moduleCompletion (either a normal completion containing a Module Record or a throw completion) and returns unused. It is used to re-enter the loading process after a call to HostLoadImportedModule. It performs the following steps when called:

  1. If state.[[IsLoading]] is false, return unused.
  2. If moduleCompletion is a normal completion, then
    1. Perform InnerModuleLoading(state, moduleCompletion.[[Value]]).
  3. Else,
    1. Set state.[[IsLoading]] to false.
    2. Perform ! Call(state.[[PromiseCapability]].[[Reject]], undefined, « moduleCompletion.[[Value]] »).
  4. Return unused.

16.2.1.5.2 Link ( )

The Link concrete method of a Cyclic Module Record module takes no arguments and returns either a normal completion containing unused or a throw completion. On success, Link transitions this module's [[Status]] from unlinked to linked. On failure, an exception is thrown and this module's [[Status]] remains unlinked. (Most of the work is done by the auxiliary function InnerModuleLinking.) It performs the following steps when called:

  1. Assert: module.[[Status]] is one of unlinked, linked, evaluating-async, or evaluated.
  2. Let stack be a new empty List.
  3. Let result be Completion(InnerModuleLinking(module, stack, 0)).
  4. If result is an abrupt completion, then
    1. For each Cyclic Module Record m of stack, do
      1. Assert: m.[[Status]] is linking.
      2. Set m.[[Status]] to unlinked.
    2. Assert: module.[[Status]] is unlinked.
    3. Return ? result.
  5. Assert: module.[[Status]] is one of linked, evaluating-async, or evaluated.
  6. Assert: stack is empty.
  7. Return unused.

16.2.1.5.2.1 InnerModuleLinking ( module, stack, index )

The abstract operation InnerModuleLinking takes arguments module (a Module Record), stack (a List of Cyclic Module Records), and index (a non-negative integer) and returns either a normal completion containing a non-negative integer or a throw completion. It is used by Link to perform the actual linking process for module, as well as recursively on all other modules in the dependency graph. The stack and index parameters, as well as a module's [[DFSIndex]] and [[DFSAncestorIndex]] fields, keep track of the depth-first search (DFS) traversal. In particular, [[DFSAncestorIndex]] is used to discover strongly connected components (SCCs), such that all modules in an SCC transition to linked together. It performs the following steps when called:

  1. If module is not a Cyclic Module Record, then
    1. Perform ? module.Link().
    2. Return index.
  2. If module.[[Status]] is one of linking, linked, evaluating-async, or evaluated, then
    1. Return index.
  3. Assert: module.[[Status]] is unlinked.
  4. Set module.[[Status]] to linking.
  5. Set module.[[DFSIndex]] to index.
  6. Set module.[[DFSAncestorIndex]] to index.
  7. Set index to index + 1.
  8. Append module to stack.
  9. For each String required of module.[[RequestedModules]], do
  10. For each ModuleRequest Record required of module.[[RequestedModules]], do
    1. Let requiredModule be GetImportedModule(module, required.[[Specifier]]).
    2. Set index to ? InnerModuleLinking(requiredModule, stack, index).
    3. If requiredModule is a Cyclic Module Record, then
      1. Assert: requiredModule.[[Status]] is one of linking, linked, evaluating-async, or evaluated.
      2. Assert: requiredModule.[[Status]] is linking if and only if stack contains requiredModule.
      3. If requiredModule.[[Status]] is linking, then
        1. Set module.[[DFSAncestorIndex]] to min(module.[[DFSAncestorIndex]], requiredModule.[[DFSAncestorIndex]]).
  11. Perform ? module.InitializeEnvironment().
  12. Assert: module occurs exactly once in stack.
  13. Assert: module.[[DFSAncestorIndex]]module.[[DFSIndex]].
  14. If module.[[DFSAncestorIndex]] = module.[[DFSIndex]], then
    1. Let done be false.
    2. Repeat, while done is false,
      1. Let requiredModule be the last element of stack.
      2. Remove the last element of stack.
      3. Assert: requiredModule is a Cyclic Module Record.
      4. Set requiredModule.[[Status]] to linked.
      5. If requiredModule and module are the same Module Record, set done to true.
  15. Return index.

16.2.1.5.3 Evaluate ( )

The Evaluate concrete method of a Cyclic Module Record module takes no arguments and returns a Promise. Evaluate transitions this module's [[Status]] from linked to either evaluating-async or evaluated. The first time it is called on a module in a given strongly connected component, Evaluate creates and returns a Promise which resolves when the module has finished evaluating. This Promise is stored in the [[TopLevelCapability]] field of the [[CycleRoot]] for the component. Future invocations of Evaluate on any module in the component return the same Promise. (Most of the work is done by the auxiliary function InnerModuleEvaluation.) It performs the following steps when called:

  1. Assert: This call to Evaluate is not happening at the same time as another call to Evaluate within the surrounding agent..
  2. Assert: None of module or any of its recursive dependencies have [[Status]] set to evaluating or a status earlier than linked.
  3. Assert: module.[[Status]] is one of linked, evaluating-async, or evaluated.
  4. If module.[[Status]] is either evaluating-async or evaluated, set module to module.[[CycleRoot]].
  5. If module.[[TopLevelCapability]] is not empty, then
    1. Return module.[[TopLevelCapability]].[[Promise]].
  6. Let stack be a new empty List.
  7. Let capability be ! NewPromiseCapability(%Promise%).
  8. Set module.[[TopLevelCapability]] to capability.
  9. Let result be Completion(InnerModuleEvaluation(module, stack, 0)).
  10. If result is an abrupt completion, then
    1. For each Cyclic Module Record m of stack, do
      1. Assert: m.[[Status]] is evaluating.
      2. Set m.[[Status]] to evaluated.
      3. Set m.[[EvaluationError]] to result.
    2. Assert: module.[[Status]] is evaluated.
    3. Assert: module.[[EvaluationError]] is result.
    4. Perform ! Call(capability.[[Reject]], undefined, « result.[[Value]] »).
  11. Else,
    1. Assert: module.[[Status]] is either evaluating-async or evaluated.
    2. Assert: module.[[EvaluationError]] is empty.
    3. If module.[[AsyncEvaluation]] is false, then
      1. Assert: module.[[Status]] is evaluated.
      2. Perform ! Call(capability.[[Resolve]], undefined, « undefined »).
    4. Assert: stack is empty.
  12. Return capability.[[Promise]].

16.2.1.5.3.1 InnerModuleEvaluation ( module, stack, index )

The abstract operation InnerModuleEvaluation takes arguments module (a Module Record), stack (a List of Cyclic Module Records), and index (a non-negative integer) and returns either a normal completion containing a non-negative integer or a throw completion. It is used by Evaluate to perform the actual evaluation process for module, as well as recursively on all other modules in the dependency graph. The stack and index parameters, as well as module's [[DFSIndex]] and [[DFSAncestorIndex]] fields, are used the same way as in InnerModuleLinking. It performs the following steps when called:

  1. If module is not a Cyclic Module Record, then
    1. Let promise be ! module.Evaluate().
    2. Assert: promise.[[PromiseState]] is not pending.
    3. If promise.[[PromiseState]] is rejected, then
      1. Return ThrowCompletion(promise.[[PromiseResult]]).
    4. Perform ? EvaluateSync(module).
    5. Return index.
  2. If module.[[Status]] is either evaluating-async or evaluated, then
    1. If module.[[EvaluationError]] is empty, return index.
    2. Otherwise, return ? module.[[EvaluationError]].
  3. If module.[[Status]] is evaluating, return index.
  4. Assert: module.[[Status]] is linked.
  5. Set module.[[Status]] to evaluating.
  6. Set module.[[DFSIndex]] to index.
  7. Set module.[[DFSAncestorIndex]] to index.
  8. Set module.[[PendingAsyncDependencies]] to 0.
  9. Set index to index + 1.
  10. Let evaluationList be a new empty List.
  11. For each ModuleRequest Record required of module.[[RequestedModules]], do
    1. Let requiredModule be GetImportedModule(module, required.[[Specifier]]).
    2. If required.[[Phase]] is defer, then
      1. Perform GatherAsynchronousTransitiveDependencies(requiredModule, evaluationList).
    3. Else if evaluationList does not contain requiredModule, then
      1. Append requiredModule to evaluationList.
  12. Append module to stack.
  13. For each String required of module.[[RequestedModules]], do
    1. Let requiredModule be GetImportedModule(module, required).
  14. For each Module Record requiredModule of evaluationList, do
    1. Set index to ? InnerModuleEvaluation(requiredModule, stack, index).
    2. If requiredModule is a Cyclic Module Record, then
      1. Assert: requiredModule.[[Status]] is one of evaluating, evaluating-async, or evaluated.
      2. Assert: requiredModule.[[Status]] is evaluating if and only if stack contains requiredModule.
      3. If requiredModule.[[Status]] is evaluating, then
        1. Set module.[[DFSAncestorIndex]] to min(module.[[DFSAncestorIndex]], requiredModule.[[DFSAncestorIndex]]).
      4. Else,
        1. Set requiredModule to requiredModule.[[CycleRoot]].
        2. Assert: requiredModule.[[Status]] is either evaluating-async or evaluated.
        3. If requiredModule.[[EvaluationError]] is not empty, return ? requiredModule.[[EvaluationError]].
      5. If requiredModule.[[AsyncEvaluation]] is true, then
        1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1.
        2. Append module to requiredModule.[[AsyncParentModules]].
  15. If module.[[PendingAsyncDependencies]] > 0 or module.[[HasTLA]] is true, then
    1. Assert: module.[[AsyncEvaluation]] is false.
    2. Set module.[[AsyncEvaluation]] to true.
    3. NOTE: The order in which module records have their [[AsyncEvaluation]] fields transition to true is significant. (See 16.2.1.5.3.4.)
    4. If module.[[PendingAsyncDependencies]] = 0, perform ExecuteAsyncModule(module).
  16. Otherwise, perform ? module.ExecuteModule().
  17. Assert: module occurs exactly once in stack.
  18. Assert: module.[[DFSAncestorIndex]]module.[[DFSIndex]].
  19. If module.[[DFSAncestorIndex]] = module.[[DFSIndex]], then
    1. Let done be false.
    2. Repeat, while done is false,
      1. Let requiredModule be the last element of stack.
      2. Remove the last element of stack.
      3. Assert: requiredModule is a Cyclic Module Record.
      4. If requiredModule.[[AsyncEvaluation]] is false, then
        1. Set requiredModule.[[Status]] to evaluated.
      5. Else,
        1. Set requiredModule.[[Status]] to evaluating-async.
      6. If requiredModule and module are the same Module Record, set done to true.
      7. Set requiredModule.[[CycleRoot]] to module.
  20. Return index.
Note 1

A module is evaluating while it is being traversed by InnerModuleEvaluation. A module is evaluated on execution completion or evaluating-async during execution if its [[HasTLA]] field is true or if it has asynchronous dependencies.

Note 2

Any modules depending on a module of an asynchronous cycle when that cycle is not evaluating will instead depend on the execution of the root of the cycle via [[CycleRoot]]. This ensures that the cycle state can be treated as a single strongly connected component through its root module state.

16.2.1.5.3.2 EvaluateSync ( module )

The abstract operation EvaluateSync takes argument module (a Module Record) and returns either a normal completion containing unused or a throw completion. It synchronously evaluates module provided it is ready for synchronous execution. It performs the following steps when called:

  1. Assert: If module is a Cyclic Module Record, module.[[HasTLA]] is false and all its asynchronous transitive dependencies have already been evaluated.
  2. Let promise be ! module.Evaluate().
  3. Assert: promise.[[PromiseState]] is either fulfilled or rejected.
  4. If promise.[[PromiseState]] is rejected, then
    1. Return ThrowCompletion(promise.[[PromiseResult]]).
  5. Return NormalCompletion(unused).

16.2.1.5.3.3 GatherAsynchronousTransitiveDependencies ( module, result [ , seen ] )

The abstract operation GatherAsynchronousTransitiveDependencies takes arguments module (a Module Record) and result (a List of Module Records) and optional argument seen (a List of Module Records) and returns unused. Collects the direct post-order list of asynchronous unexecuted transitive dependencies, stopping the depth-first search for a branch when an async dependency is found. It performs the following steps when called:

  1. If seen is not specified, let seen be a new empty List.
  2. If seen contains module, return unused.
  3. Append module to seen.
  4. If module is not a Cyclic Module Record, return unused.
  5. If module.[[Status]] is either evaluating or evaluated, return unused.
  6. If module.[[HasTLA]] is true, then
    1. If result does not contain module, append module to result.
    2. Return unused.
  7. For each ModuleRequest Record required of module.[[RequestedModules]], do
    1. Let requiredModule be GetImportedModule(module, required.[[Specifier]]).
    2. Perform GatherAsynchronousTransitiveDependencies(requiredModule, result, seen).
  8. Return unused.

16.2.1.5.4 Example Cyclic Module Record Graphs

This non-normative section gives a series of examples of the linking and evaluation of a few common module graphs, with a specific focus on how errors can occur.

First consider the following simple module graph:

Figure 1: A simple module graph
A module graph in which module A depends on module B, and module B depends on module C

Let's first assume that there are no error conditions. When a host first calls A.LoadRequestedModules(), this will complete successfully by assumption, and recursively load the dependencies of B and C as well (respectively, C and none), and then set A.[[Status]] = B.[[Status]] = C.[[Status]] = unlinked. Then, when the host calls A.Link(), it will complete successfully (again by assumption) such that A.[[Status]] = B.[[Status]] = C.[[Status]] = linked. These preparatory steps can be performed at any time. Later, when the host is ready to incur any possible side effects of the modules, it can call A.Evaluate(), which will complete successfully, returning a Promise resolving to undefined (again by assumption), recursively having evaluated first C and then B. Each module's [[Status]] at this point will be evaluated.

Consider then cases involving linking errors, after a successful call to A.LoadRequestedModules(). If InnerModuleLinking of C succeeds but, thereafter, fails for B, for example because it imports something that C does not provide, then the original A.Link() will fail, and both A and B's [[Status]] remain unlinked. C's [[Status]] has become linked, though.

Finally, consider a case involving evaluation errors after a successful call to Link(). If InnerModuleEvaluation of C succeeds but, thereafter, fails for B, for example because B contains code that throws an exception, then the original A.Evaluate() will fail, returning a rejected Promise. The resulting exception will be recorded in both A and B's [[EvaluationError]] fields, and their [[Status]] will become evaluated. C will also become evaluated but, in contrast to A and B, will remain without an [[EvaluationError]], as it successfully completed evaluation. Storing the exception ensures that any time a host tries to reuse A or B by calling their Evaluate() method, it will encounter the same exception. (Hosts are not required to reuse Cyclic Module Records; similarly, hosts are not required to expose the exception objects thrown by these methods. However, the specification enables such uses.)

Now consider a different type of error condition:

Figure 2: A module graph with an unresolvable module
A module graph in which module A depends on a missing (unresolvable) module, represented by ???

In this scenario, module A declares a dependency on some other module, but no Module Record exists for that module, i.e. HostLoadImportedModule calls FinishLoadingImportedModule with an exception when asked for it. This could occur for a variety of reasons, such as the corresponding resource not existing, or the resource existing but ParseModule returning some errors when trying to parse the resulting source text. Hosts can choose to expose the cause of failure via the completion they pass to FinishLoadingImportedModule. In any case, this exception causes a loading failure, which results in A's [[Status]] remaining new.

The difference here between loading, linking and evaluation errors is due to the following characteristic:

  • Evaluation must be only performed once, as it can cause side effects; it is thus important to remember whether evaluation has already been performed, even if unsuccessfully. (In the error case, it makes sense to also remember the exception because otherwise subsequent Evaluate() calls would have to synthesize a new one.)
  • Linking, on the other hand, is side-effect-free, and thus even if it fails, it can be retried at a later time with no issues.
  • Loading closely interacts with the host, and it may be desiderable for some of them to allow users to retry failed loads (for example, if the failure is caused by temporarily bad network conditions).

Now, consider a module graph with a cycle:

Figure 3: A cyclic module graph
A module graph in which module A depends on module B and C, but module B also depends on module A

Here we assume that the entry point is module A, so that the host proceeds by calling A.LoadRequestedModules(), which performs InnerModuleLoading on A. This in turn calls InnerModuleLoading on B and C. Because of the cycle, this again triggers InnerModuleLoading on A, but at this point it is a no-op since A's dependencies loading has already been triggered during this LoadRequestedModules process. When all the modules in the graph have been successfully loaded, their [[Status]] transitions from new to unlinked at the same time.

Then the host proceeds by calling A.Link(), which performs InnerModuleLinking on A. This in turn calls InnerModuleLinking on B. Because of the cycle, this again triggers InnerModuleLinking on A, but at this point it is a no-op since A.[[Status]] is already linking. B.[[Status]] itself remains linking when control gets back to A and InnerModuleLinking is triggered on C. After this returns with C.[[Status]] being linked, both A and B transition from linking to linked together; this is by design, since they form a strongly connected component. It's possible to transition the status of modules in the same SCC at the same time because during this phase the module graph is traversed with a depth-first search.

An analogous story occurs for the evaluation phase of a cyclic module graph, in the success case.

Now consider a case where A has a linking error; for example, it tries to import a binding from C that does not exist. In that case, the above steps still occur, including the early return from the second call to InnerModuleLinking on A. However, once we unwind back to the original InnerModuleLinking on A, it fails during InitializeEnvironment, namely right after C.ResolveExport(). The thrown SyntaxError exception propagates up to A.Link, which resets all modules that are currently on its stack (these are always exactly the modules that are still linking). Hence both A and B become unlinked. Note that C is left as linked.

Alternatively, consider a case where A has an evaluation error; for example, its source code throws an exception. In that case, the evaluation-time analog of the above steps still occurs, including the early return from the second call to InnerModuleEvaluation on A. However, once we unwind back to the original InnerModuleEvaluation on A, it fails by assumption. The exception thrown propagates up to A.Evaluate(), which records the error in all modules that are currently on its stack (i.e., the modules that are still evaluating) as well as via [[AsyncParentModules]], which form a chain for modules which contain or depend on top-level await through the whole dependency graph through the AsyncModuleExecutionRejected algorithm. Hence both A and B become evaluated and the exception is recorded in both A and B's [[EvaluationError]] fields, while C is left as evaluated with no [[EvaluationError]].

Lastly, consider a module graph with a cycle, where all modules complete asynchronously:

Figure 4: An asynchronous cyclic module graph
A module graph in which module A depends on module B and C, module B depends on module D, module C depends on module D and E, and module D depends on module A

Loading and linking happen as before, and all modules end up with [[Status]] set to linked.

Calling A.Evaluate() calls InnerModuleEvaluation on A, B, and D, which all transition to evaluating. Then InnerModuleEvaluation is called on A again, which is a no-op because it is already evaluating. At this point, D.[[PendingAsyncDependencies]] is 0, so ExecuteAsyncModule(D) is called and we call D.ExecuteModule with a new PromiseCapability tracking the asynchronous execution of D. We unwind back to the InnerModuleEvaluation on B, setting B.[[PendingAsyncDependencies]] to 1 and B.[[AsyncEvaluation]] to true. We unwind back to the original InnerModuleEvaluation on A, setting A.[[PendingAsyncDependencies]] to 1. In the next iteration of the loop over A's dependencies, we call InnerModuleEvaluation on C and thus on D (again a no-op) and E. As E has no dependencies and is not part of a cycle, we call ExecuteAsyncModule(E) in the same manner as D and E is immediately removed from the stack. We unwind once more to the original InnerModuleEvaluation on A, setting C.[[AsyncEvaluation]] to true. Now we finish the loop over A's dependencies, set A.[[AsyncEvaluation]] to true, and remove the entire strongly connected component from the stack, transitioning all of the modules to evaluating-async at once. At this point, the fields of the modules are as given in Table 7.

Table 7: Module fields after the initial Evaluate() call
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A 0 0 evaluating-async true « » 2 (B and C)
B 1 0 evaluating-async true « A » 1 (D)
C 2 0 evaluating-async true « A » 2 (D and E)
D 3 0 evaluating-async true « B, C » 0
E 4 4 evaluating-async true « C » 0

Let us assume that E finishes executing first. When that happens, AsyncModuleExecutionFulfilled is called, E.[[Status]] is set to evaluated and C.[[PendingAsyncDependencies]] is decremented to become 1. The fields of the updated modules are as given in Table 8.

Table 8: Module fields after module E finishes executing
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
C 2 0 evaluating-async true « A » 1 (D)
E 4 4 evaluated true « C » 0

D is next to finish (as it was the only module that was still executing). When that happens, AsyncModuleExecutionFulfilled is called again and D.[[Status]] is set to evaluated. Then B.[[PendingAsyncDependencies]] is decremented to become 0, ExecuteAsyncModule is called on B, and it starts executing. C.[[PendingAsyncDependencies]] is also decremented to become 0, and C starts executing (potentially in parallel to B if B contains an await). The fields of the updated modules are as given in Table 9.

Table 9: Module fields after module D finishes executing
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
B 1 0 evaluating-async true « A » 0
C 2 0 evaluating-async true « A » 0
D 3 0 evaluated true « B, C » 0

Let us assume that C finishes executing next. When that happens, AsyncModuleExecutionFulfilled is called again, C.[[Status]] is set to evaluated and A.[[PendingAsyncDependencies]] is decremented to become 1. The fields of the updated modules are as given in Table 10.

Table 10: Module fields after module C finishes executing
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A 0 0 evaluating-async true « » 1 (B)
C 2 0 evaluated true « A » 0

Then, B finishes executing. When that happens, AsyncModuleExecutionFulfilled is called again and B.[[Status]] is set to evaluated. A.[[PendingAsyncDependencies]] is decremented to become 0, so ExecuteAsyncModule is called and it starts executing. The fields of the updated modules are as given in Table 11.

Table 11: Module fields after module B finishes executing
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A 0 0 evaluating-async true « » 0
B 1 0 evaluated true « A » 0

Finally, A finishes executing. When that happens, AsyncModuleExecutionFulfilled is called again and A.[[Status]] is set to evaluated. At this point, the Promise in A.[[TopLevelCapability]] (which was returned from A.Evaluate()) is resolved, and this concludes the handling of this module graph. The fields of the updated module are as given in Table 12.

Table 12: Module fields after module A finishes executing
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A 0 0 evaluated true « » 0

Alternatively, consider a failure case where C fails execution and returns an error before B has finished executing. When that happens, AsyncModuleExecutionRejected is called, which sets C.[[Status]] to evaluated and C.[[EvaluationError]] to the error. It then propagates this error to all of the AsyncParentModules by performing AsyncModuleExecutionRejected on each of them. The fields of the updated modules are as given in Table 13.

Table 13: Module fields after module C finishes with an error
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[EvaluationError]]
A 0 0 evaluated true « » 1 (B) empty
C 2 1 evaluated true « A » 0 C's evaluation error

A will be rejected with the same error as C since C will call AsyncModuleExecutionRejected on A with C's error. A.[[Status]] is set to evaluated. At this point the Promise in A.[[TopLevelCapability]] (which was returned from A.Evaluate()) is rejected. The fields of the updated module are as given in Table 14.

Table 14: Module fields after module A is rejected
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[EvaluationError]]
A 0 0 evaluated true « » 0 C's Evaluation Error

Then, B finishes executing without an error. When that happens, AsyncModuleExecutionFulfilled is called again and B.[[Status]] is set to evaluated. GatherAvailableAncestors is called on B. However, A.[[CycleRoot]] is A which has an evaluation error, so it will not be added to the returned sortedExecList and AsyncModuleExecutionFulfilled will return without further processing. Any future importer of B will resolve the rejection of B.[[CycleRoot]].[[EvaluationError]] from the evaluation error from C that was set on the cycle root A. The fields of the updated modules are as given in Table 15.

Table 15: Module fields after module B finishes executing in an erroring graph
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[EvaluationError]]
A 0 0 evaluated true « » 0 C's Evaluation Error
B 1 0 evaluated true « A » 0 empty

16.2.1.5.4.1 Example Cyclic Module Record Graphs with Deferred Imports

Deferred imports complicate handling of module graphs, because they allow deferring evaluation of part of the graph while still eagerly evaluating the asynchronous subgraphs of a deferred portion. In this section, deferred imports are marked with dashed arrows and modules using top-level await are marked with TLA.

Consider the following graph, assuming that all the modules have already their [[Status]] set to linked:

Figure 5: A simple module graph with a deferred import
A module graph in which module A depends on module B with a deferred import, and module B depends on module C

Calling A.Evaluate() calls InnerModuleEvaluation A, which transitions A.[[Status]] to evaluating. A has a deferred import for B, so it performs GatherAsynchronousTransitiveDependencies(B). B doesn't have any transitive dependency using top-level await, so that AO doesn't collect any module. A also imports D, so the list of modules that A's evaluation depends on is « D »: the InnerModuleEvaluation algorithm run on A performs InnerModuleEvaluation on D, transitioning its [[Status]] to evaluating and then evaluated, and then it calls A.ExecuteModule(). If A's execution triggers B.Evaluate(), then InnerModuleEvaluation will be called on B and C executing them and transitioning their [[Status]] to evaluated. Finally, A.[[Status]] transitions to evaluated.

Consider the same graph, but with C using top-level await:

Figure 6: A simple module graph with a deferred import and a transitive asynchronous dependency
A module graph in which module A depends on module B with a deferred import, and module B depends on module C, which uses top-level await.

Calling A.Evaluate() calls InnerModuleEvaluation A, which transitions A.[[Status]] to evaluating. It then performs GatherAsynchronousTransitiveDependencies(B), finding the module C: the list of modules whose evaluation A's evaluation depends on is « C, D ». It then calls InnerModuleEvaluation on C and D, transitioning their [[Status]] respectively to evaluating-async and evaluated and registering A's evaluation as pending on the asynchronous dependency C. At this point, the fields of the modules are given in Table 16:

Table 16: Module fields after the initial Evaluate() call
Module [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A evaluating-async true « » 1 (C)
B linked false « » 0
C evaluating-async true « A » 0
D evaluated false « » 0

Let us assume that C succesfully finishes evaluating. When that happens, AsyncModuleExecutionFulfilled(C) is called, C.[[Status]] is set to evaluated, A.ExecuteModule() is called and upon successful completion A.[[Status]] is set to evaluted. The fields of the modules are now as given in Table 17:

Table 17: Module fields after C finishes executing
Module [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A evaluated true « » 0
B linked false « » 0
C evaluated true « A » 0
D evaluated false « » 0

If later B.Evaluate() is called, then the InnerModuleEvaluation call on B will transition B.[[Status]] to evaluating and will call InnerModuleEvaluation on C. Since at this point C.[[Status]] is already evaluated, B.ExecuteModule() will be called synchronously and B.[[Status]] will synchronously transition to evaluated.

Alternatively, consider a failure case where C fails to execute with an exception error. When that happens, AsyncModuleExecutionRejected(C, error) is called, it sets C.[[Status]] = A.[[Status]] to evaluated and C.[[EvaluationError]] = A.[[EvaluationError]] to ThrowCompletion(error). The fields of the modules are this as given in Table 18:

Table 18: Module fields after C finishes executing with an exception
Module [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[EvaluationError]]
A evaluated true « » 1 (C) error
B linked false « » 0 empty
C evaluated true « B » 0 error
D evaluated false « » 0 empty

If at any later point in time B.Evaluate() is called, it will perform InnerModuleEvaluation on C which immediately returns with ThrowCompletion(error), transitioning B.[[Status]] to evalauted and setting B.[[EvaluationError]] to error.

Consider now a graph with a deferred import taking part in a dependencies cycle, and assume that the evaluation of A calls B.Evaluate():

Figure 7: A module graph with a deferred import and a cycle back to the root
A module graph in which module A depends on module B with a deferred import, module B depends on module C, which uses top-level await and depends on A

As in the previous example, calling A.Evaluate() calls InnerModuleEvaluation on A, GatherAsynchronousTransitiveDependencies on B will find C and thus it performs InnerModuleEvaluation on C, transitioning their A and C's [[Status]] to evaluating-async. When InnerModuleEvaluation iterates over C's dependencies, it finds A and sets A.[[CycleRoot]] = C.[[CycleRoot]] = A. At this point, the fields of the modules are given in Table 19:

Table 19: Module fields after the initial Evaluate() call
Module [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[CycleRoot]]
A evaluating-async true « » 1 (C) A
B linked false « » 0 empty
C evaluating-async true « A » 0 A

Assume now that C succesfully finishes evaluating. When that happens AsyncModuleExecutionFulfilled(C) is called, it sets C.[[Status]] to evaluated, and it performs A.ExecuteModule(). As stated, A's execution causes a call to B.Evaluate(), which calls InnerModuleEvaluation on B and on C. Since C.[[Status]] is evaluated, B.ExecuteModule() is called synchronously and, assuming it completes succesfully, transitions B.[[Status]] to evaluated. At this point, the fields of the modules are given in Table 20:

Table 20: Module fields after the B.ExecuteModule() call
Module [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[CycleRoot]]
A evaluating-async true « » 0 A
B evaluated false « » 0 B
C evaluated true « A » 0 A

Once the call to A.ExecuteModule() completes, A.[[Status]] transitions to evaluated.

16.2.2 Imports

Syntax

ImportDeclaration : import deferopt ImportClause FromClause ; import ModuleSpecifier ; ImportClause : ImportedDefaultBinding NameSpaceImport NamedImports ImportedDefaultBinding , NameSpaceImport ImportedDefaultBinding , NamedImports ImportedDefaultBinding : ImportedBinding NameSpaceImport : * as ImportedBinding NamedImports : { } { ImportsList } { ImportsList , } FromClause : from ModuleSpecifier ImportsList : ImportSpecifier ImportsList , ImportSpecifier ImportSpecifier : ImportedBinding ModuleExportName as ImportedBinding ModuleSpecifier : StringLiteral ImportedBinding : BindingIdentifier[~Yield, +Await]

16.2.2.1 Static Semantics: Early Errors

ModuleItem : ImportDeclaration ImportDeclaration : import defer ImportClause FromClause ;

16.2.2.2 Static Semantics: ImportEntries

The syntax-directed operation ImportEntries takes no arguments and returns a List of ImportEntry Records. It is defined piecewise over the following productions:

Module : [empty]
  1. Return a new empty List.
ModuleItemList : ModuleItemList ModuleItem
  1. Let entries1 be ImportEntries of ModuleItemList.
  2. Let entries2 be ImportEntries of ModuleItem.
  3. Return the list-concatenation of entries1 and entries2.
ModuleItem : ExportDeclaration StatementListItem
  1. Return a new empty List.
ImportDeclaration : import deferopt ImportClause FromClause ;
  1. Let module be the sole element of ModuleRequestsSV of FromClause.
  2. Return ImportEntriesForModule of ImportClause with arguments module.
ImportDeclaration : import ModuleSpecifier ;
  1. Return a new empty List.

16.2.3 Exports

Syntax

ExportDeclaration : export ExportFromClause FromClause ; export NamedExports ; export VariableStatement[~Yield, +Await] export Declaration[~Yield, +Await] export default HoistableDeclaration[~Yield, +Await, +Default] export default ClassDeclaration[~Yield, +Await, +Default] export default [lookahead ∉ { function, async [no LineTerminator here] function, class }] AssignmentExpression[+In, ~Yield, +Await] ; ExportFromClause : * * as ModuleExportName NamedExports NamedExports : { } { ExportsList } { ExportsList , } ExportsList : ExportSpecifier ExportsList , ExportSpecifier ExportSpecifier : ModuleExportName ModuleExportName as ModuleExportName

16.2.3.1 Static Semantics: Early Errors

ExportDeclaration : export NamedExports ; Note

The above rule means that each ReferencedBindings of NamedExports is treated as an IdentifierReference.

16.2.3.3 Static Semantics: ExportedNames

The syntax-directed operation ExportedNames takes no arguments and returns a List of Strings.

Note

ExportedNames are the externally visible names that a Module explicitly maps to one of its local name bindings.

It is defined piecewise over the following productions:

ModuleItemList : ModuleItemList ModuleItem
  1. Let names1 be ExportedNames of ModuleItemList.
  2. Let names2 be ExportedNames of ModuleItem.
  3. Return the list-concatenation of names1 and names2.
ModuleItem : ExportDeclaration
  1. Return the ExportedNames of ExportDeclaration.
ModuleItem : ImportDeclaration StatementListItem
  1. Return a new empty List.
ExportFromClause : *
  1. Return a new empty List.
ExportFromClause : * as ModuleExportName
  1. Return a List whose sole element is the StringValue of ModuleExportName.
ExportFromClause : NamedExports
  1. Return the ExportedNames of NamedExports.
ExportDeclaration : export VariableStatement
  1. Return the BoundNames of VariableStatement.
ExportDeclaration : export Declaration
  1. Return the BoundNames of Declaration.
ExportDeclaration : export default HoistableDeclaration export default ClassDeclaration export default AssignmentExpression ;
  1. Return « "default" ».
NamedExports : { }
  1. Return a new empty List.
ExportsList : ExportsList , ExportSpecifier
  1. Let names1 be the ExportedNames of ExportsList.
  2. Let names2 be the ExportedNames of ExportSpecifier.
  3. Return the list-concatenation of names1 and names2.
ExportSpecifier : ModuleExportName
  1. Return a List whose sole element is the StringValue of ModuleExportName.
ExportSpecifier : ModuleExportName as ModuleExportName
  1. Return a List whose sole element is the StringValue of the second ModuleExportName.

16.2.3.4 Static Semantics: ExportEntries

The syntax-directed operation ExportEntries takes no arguments and returns a List of ExportEntry Records. It is defined piecewise over the following productions:

Module : [empty]
  1. Return a new empty List.
ModuleItemList : ModuleItemList ModuleItem
  1. Let entries1 be ExportEntries of ModuleItemList.
  2. Let entries2 be ExportEntries of ModuleItem.
  3. Return the list-concatenation of entries1 and entries2.
ModuleItem : ImportDeclaration StatementListItem
  1. Return a new empty List.
ExportDeclaration : export NamedExports ;
  1. Return ExportEntriesForModule of NamedExports with argument null.
ExportDeclaration : export VariableStatement
  1. Let entries be a new empty List.
  2. Let names be the BoundNames of VariableStatement.
  3. For each element name of names, do
    1. Append the ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: name, [[ExportName]]: name } to entries.
  4. Return entries.
ExportDeclaration : export Declaration
  1. Let entries be a new empty List.
  2. Let names be the BoundNames of Declaration.
  3. For each element name of names, do
    1. Append the ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: name, [[ExportName]]: name } to entries.
  4. Return entries.
ExportDeclaration : export default HoistableDeclaration
  1. Let names be BoundNames of HoistableDeclaration.
  2. Let localName be the sole element of names.
  3. Return a List whose sole element is a new ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: localName, [[ExportName]]: "default" }.
ExportDeclaration : export default ClassDeclaration
  1. Let names be BoundNames of ClassDeclaration.
  2. Let localName be the sole element of names.
  3. Return a List whose sole element is a new ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: localName, [[ExportName]]: "default" }.
ExportDeclaration : export default AssignmentExpression ;
  1. Let entry be the ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: "*default*", [[ExportName]]: "default" }.
  2. Return « entry ».
Note

"*default*" is used within this specification as a synthetic name for anonymous default export values. See this note for more details.

A Copyright & Software License

Copyright Notice

© 2024 Nicolò Ribaudo

Software License

All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  3. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.