Stage 3 Draft / May 28, 2021

Top-Level Await

Introduction

Top-Level Await allows the await keyword to be used at the top level of the module goal. See the explainer for the motivation, context, and high-level semantics.

1 AsyncFunctionStart ( promiseCapability, asyncFunctionBody )

The abstract operation AsyncFunctionStart takes arguments promiseCapability and asyncFunctionBody. It performs the following steps when called:

  1. Let runningContext be the running execution context.
  2. Let asyncContext be a copy of runningContext.
  3. NOTE: Copying the execution state is required for the step belowAsyncBlockStart to resume its execution. It is ill-defined to resume a currently executing context.
  4. Perform ! AsyncBlockStart(promiseCapability, asyncFunctionBody, asyncContext).
  5. Set the code evaluation state of asyncContext such that when evaluation is resumed for that execution context the following steps will be performed:
    1. Let result be the result of evaluating asyncFunctionBody.
    2. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done.
    3. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
    4. If result.[[Type]] is normal, then
      1. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »).
    5. Else if result.[[Type]] is return, then
      1. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »).
    6. Else,
      1. Assert: result.[[Type]] is throw.
      2. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »).
    7. Return.
  6. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
  7. Resume the suspended evaluation of asyncContext. Let result be the value returned by the resumed computation.
  8. Assert: When we return here, asyncContext has already been removed from the execution context stack and runningContext is the currently running execution context.
  9. Assert: result is a normal completion with a value of undefined. The possible sources of completion values are Await or, if the async function doesn't await anything, the step 4.g above.
  10. Return.

2 AsyncBlockStart ( promiseCapability, asyncBody, asyncContext )

  1. Assert: promiseCapability is a PromiseCapability Record.
  2. Let runningContext be the running execution context.
  3. Set the code evaluation state of asyncContext such that when evaluation is resumed for that execution context the following steps will be performed:
    1. Let result be the result of evaluating asyncBody.
    2. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done.
    3. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
    4. If result.[[Type]] is normal, then
      1. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »).
    5. Else if result.[[Type]] is return, then
      1. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »).
    6. Else,
      1. Assert: result.[[Type]] is throw.
      2. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »).
    7. Return.
  4. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
  5. Resume the suspended evaluation of asyncContext. Let result be the value returned by the resumed computation.
  6. Assert: When we return here, asyncContext has already been removed from the execution context stack and runningContext is the currently running execution context.
  7. Assert: result is a normal completion with a value of undefined. The possible sources of completion values are Await or, if the async function doesn't await anything, the step 2.g above.

3 HostImportModuleDynamically ( referencingScriptOrModule, specifier, promiseCapability )

HostImportModuleDynamically is a host-defined abstract operation that performs any necessary setup work in order to make available the module corresponding to the ModuleSpecifier String, specifier, occurring within the context of the script or module represented by the Script Record or Module Record referencingScriptOrModule. (referencingScriptOrModule may also be null, if there is no active script or module when the import() expression occurs.) It then performs FinishDynamicImport to finish the dynamic import process.

The implementation of HostImportModuleDynamically must conform to the following requirements:

The actual process performed is host-defined, but typically consists of performing whatever I/O operations are necessary to allow HostResolveImportedModule to synchronously retrieve the appropriate Module Record, and then calling its Evaluate concrete method. This might require performing similar normalization as HostResolveImportedModule does.

4 FinishDynamicImport ( referencingScriptOrModule, specifier, promiseCapability, completioninnerPromise )

The abstract operation FinishDynamicImport takes arguments referencingScriptOrModule, specifier, promiseCapability (a PromiseCapability Record), and innerPromise. FinishDynamicImport completes the process of a dynamic import originally started by an import() call, resolving or rejecting the promise returned by that call as appropriate according to innerPromise. It is performed by host environments as part of HostImportModuleDynamically. It performs the following steps when called:

  1. If completion is an abrupt completion, then perform ! Call(promiseCapability.[[Reject]], undefined, « completion.[[Value]] »).
  2. Else,
    1. Assert: completion is a normal completion and completion.[[Value]] is undefined.
    2. Let moduleRecord be ! HostResolveImportedModule(referencingScriptOrModule, specifier).
    3. Assert: Evaluate has already been invoked on moduleRecord and successfully completed.
    4. Let namespace be GetModuleNamespace(moduleRecord).
    5. If namespace is an abrupt completion, perform ! Call(promiseCapability.[[Reject]], undefined, « namespace.[[Value]] »).
    6. Else, perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace.[[Value]] »).
  3. Let stepsFulfilled be the steps of a CallDynamicImportResolved function as specified below.
  4. Let onFulfilled be CreateBuiltinFunction(stepsFulfilled, « »).
  5. Let stepsRejected be the steps of a CallDynamicImportRejected function as specified below.
  6. Let onRejected be CreateBuiltinFunction(stepsRejected, « »).
  7. Perform ! PerformPromiseThen(innerPromise.[[Promise]], onFulfilled, onRejected).

A CallDynamicImportResolved function is an anonymous built-in function with no internal slots and has one argument result. When a CallDynamicImportResolved function is called it performs the following steps:

  1. Assert: result is undefined.
  2. Let moduleRecord be ! HostResolveImportedModule(referencingScriptOrModule, specifier).
  3. Assert: Evaluate has already been invoked on moduleRecord and successfully completed.
  4. Let namespace be GetModuleNamespace(moduleRecord).
  5. If namespace is an abrupt completion, perform ! Call(promiseCapability.[[Reject]], undefined, « namespace.[[Value]] »).
  6. Else, perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace.[[Value]] »).

A CallDynamicImportRejected function is an anonymous built-in function with no internal slots and has one argument error. When a CallDynamicImportRejected function is called it performs the following steps:

  1. Perform ! Call(promiseCapability.[[Reject]], undefined, « error »).

5 Abstract Module Records

Table 1: Module Record Fields
Field Name Value Type Meaning
[[Realm]] Realm Record The Realm within which this module was created.
[[Environment]] module Environment Record | empty The Environment Record containing the top level bindings for this module. This field is set when the module is linked.
[[Namespace]] Object | empty The Module Namespace Object (26.3) if one has been created for this module.
[[HostDefined]] Any, default value is undefined. Field reserved for use by host environments that need to associate additional information with a module.
Table 2: Abstract Methods of Module Records
Method Purpose
GetExportedNames(exportStarSet) Return a list of all names that are either directly or indirectly exported from this module.
ResolveExport(exportName, resolveSet)

Return the binding of a name exported by this module. Bindings are represented by a ResolvedBinding Record, of the form { [[Module]]: Module Record, [[BindingName]]: String }. If the export is a Module Namespace Object without a direct binding in any module, [[BindingName]] will be set to "*namespace*". Return null if the name cannot be resolved, or "ambiguous" if multiple bindings were found.

Each time this operation is called with a specific exportName, resolveSet pair as arguments it must return the same result if it completes normally.

Link()

Prepare the module for evaluation by transitively resolving all module dependencies and creating a module Environment Record.

Evaluate()

Returns a promise for the evaluation of this module and its dependencies, resolving on successful evaluation or if it has already been evaluated successfully, and rejecting for an evaluation error or If this module has already been evaluated successfully, return undefined; if it has already been evaluated unsuccessfully, throw the exception that was produced. Otherwise, Transitively evaluate all module dependencies of this module and then evaluate this module. If the promise is rejected, hosts are expected to handle the promise rejection and rethrow the evaluation error.

Link must have completed successfully prior to invoking this method.

6 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 1 Cyclic Module Records have the additional fields listed in Table 3

Table 3: Additional Fields of Cyclic Module Records
Field Name Value Type Meaning
[[Status]] unlinked | linking | linked | evaluating | evaluating-async | evaluated Initially unlinked. Transitions to 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 async dependencies or it is an async module that has been executed and is pending top-level completion.
[[EvaluationError]] An abrupt completion | undefined A completion of type throw representing the exception that occurred during evaluation. undefined if no exception occurred or if [[Status]] is not evaluated.
[[DFSIndex]] Integer | empty Auxiliary field used during Link and Evaluate only. If [[Status]] is linking or evaluating, this nonnegative number records the point at which the module was first visited during the depth-first traversal of the dependency graph.
[[DFSAncestorIndex]] Integer | empty Auxiliary field used during Link and Evaluate only. If [[Status]] is linking or evaluating, this is either the module's own [[DFSIndex]] or that of an "earlier" module in the same strongly connected component.
[[RequestedModules]] List of String A List of all the ModuleSpecifier strings used by the module represented by this record to request the importation of a module. The List is source code occurrence ordered.
[[CycleRoot]] Cyclic Module Record | 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.
[[Async]] 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 make the module asynchronous. This field must not change after the module is parsed.
[[AsyncEvaluation]] Boolean Whether this module is either itself async or has an asynchronous dependency. Note: The order in which this field is set is used to order queued executions, see 6.2.4.
[[TopLevelCapability]] Promise Capability | empty If Evaluate() was called on this module, this field contains the Promise capability 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 any of those dependencies.
[[AsyncParentModules]] List of Cyclic Module Records If this module or a dependency has [[Async]] 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]] Integer | empty This tracks the number of async dependency modules remaining to execute for this module if it has any asynchronous dependencies. A module with async dependencies will be executed when this field reaches 0 and there are no execution errors.

In addition to the methods defined in Table 2 Cyclic Module Records have the additional methods listed in Table 4

Table 4: 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 [[Async]], then a Promise Capability 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 Promise Capability if necessary.

6.1 Link ( ) Concrete Method

The Link concrete method of a Cyclic Module Record implements the corresponding Module Record abstract method.

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.

This abstract method performs the following steps (most of the work is done by the auxiliary function InnerModuleLinking):

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

6.1.1 InnerModuleLinking ( module, stack, index )

The abstract operation InnerModuleLinking takes arguments module (a Cyclic Module Record), stack, and index. 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 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 that is an element of module.[[RequestedModules]], do
    1. Let requiredModule be ? HostResolveImportedModule(module, required).
    2. Set index to ? InnerModuleLinking(requiredModule, stack, index).
    3. If requiredModule is a Cyclic Module Record, then
      1. Assert: requiredModule.[[Status]] is either linking, linked, evaluating-async or evaluated.
      2. Assert: requiredModule.[[Status]] is linking if and only if requiredModule is in stack.
      3. If requiredModule.[[Status]] is linking, then
        1. Set module.[[DFSAncestorIndex]] to min(module.[[DFSAncestorIndex]], requiredModule.[[DFSAncestorIndex]]).
  10. Perform ? module.InitializeEnvironment().
  11. Assert: module occurs exactly once in stack.
  12. Assert: module.[[DFSAncestorIndex]] is less than or equal to module.[[DFSIndex]].
  13. If module.[[DFSAncestorIndex]] equals module.[[DFSIndex]], then
    1. Let done be false.
    2. Repeat, while done is false,
      1. Let requiredModule be the last element in 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.
  14. Return index.

6.2 Evaluate ( ) Concrete Method

The Evaluate concrete method of a Cyclic Module Record implements the corresponding Module Record abstract method.

Evaluate transitions this module's [[Status]] from linked to evaluating-async orevaluated.

If execution results in an exception, that exception is recorded in the [[EvaluationError]] field and rethrown by future invocations of Evaluate.

The Promise returned by Evaluate is allocated by the first invocation of Evaluate, and its capability is stored in the [[TopLevelCapability]] field. If execution results in an exception, the Promise is rejected. Future invocations of Evaluate return the same Promise.

This abstract method performs the following steps (most of the work is done by the auxiliary function InnerModuleEvaluation):

  1. Assert: This call to Evaluate is not happening at the same time as another call to Evaluate within the surrounding agent.
  2. Let module be this Cyclic Module Record.
  3. Assert: module.[[Status]] is linked, evaluating-async or evaluated.
  4. If module.[[Status]] is 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 InnerModuleEvaluation(module, stack, 0).
  10. If result is an abrupt completion, then
    1. For each Cyclic Module Record m in 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 and module.[[EvaluationError]] is result.
    3. Return result.
    4. Perform ! Call(capability.[[Reject]], undefined, « result.[[Value]] »).
  11. Otherwise,
    1. Assert: module.[[Status]] is evaluating-async orevaluated and module.[[EvaluationError]] is empty.
    2. If module.[[AsyncEvaluation]] is false, then
      1. Assert: module.[[Status]] is evaluated.
      2. Perform ! Call(capability.[[Resolve]], undefined, « undefined »).
    3. Assert: stack is empty.
  12. Return undefinedcapability.[[Promise]].

6.2.1 InnerModuleEvaluation ( module, stack, index )

The abstract operation InnerModuleEvaluation takes arguments module (a Source Text Module Record), stack, and index. 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.[[PromiseStateResult]]).
    4. Return index.
  2. If module.[[Status]] is evaluated or evaluating-async, 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. Append module to stack.
  11. For each String required that is an element of module.[[RequestedModules]], do
    1. Let requiredModule be ! HostResolveImportedModule(module, required).
    2. NOTE: Link must be completed successfully prior to invoking this method, so every requested module is guaranteed to resolve successfully.
    3. Set index to ? InnerModuleEvaluation(requiredModule, stack, index).
    4. If requiredModule is a Cyclic Module Record, then
      1. Assert: requiredModule.[[Status]] is either evaluating, evaluating-async or evaluated.
      2. Assert: requiredModule.[[Status]] is evaluating if and only if requiredModule is in stack.
      3. If requiredModule.[[Status]] is evaluating, then
        1. Set module.[[DFSAncestorIndex]] to min(module.[[DFSAncestorIndex]], requiredModule.[[DFSAncestorIndex]]).
      4. Otherwise,
        1. Set requiredModule to requiredModule.[[CycleRoot]].
        2. Assert: requiredModule.[[Status]] is 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]].
  12. Perform ? module.ExecuteModule().
  13. If module.[[PendingAsyncDependencies]] > 0 or module.[[Async]] is true, then
    1. Assert: module.[[AsyncEvaluation]] is false.
    2. Set module.[[AsyncEvaluation]] to true.
    3. NOTE: The order in which [[AsyncEvaluation]] transitions to true is significant. (See 6.2.4)
    4. If module.[[PendingAsyncDependencies]] is 0, perform ! ExecuteAsyncModule(module).
  14. Otherwise, perform ? module.ExecuteModule().
  15. Assert: module occurs exactly once in stack.
  16. Assert: module.[[DFSAncestorIndex]] is less than or equal to module.[[DFSIndex]].
  17. If module.[[DFSAncestorIndex]] equals module.[[DFSIndex]], then
    1. Let done be false.
    2. Repeat, while done is false,
      1. Let requiredModule be the last element in stack.
      2. Remove the last element of stack.
      3. Assert: requiredModule is a Cyclic Module Record.
      4. If requiredModule.[[AsyncEvaluation]] is false, set requiredModule.[[Status]] to evaluated.
      5. Otherwise, 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.
  18. 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 it is an asynchronous module.

Note 2

Any modules depending on a module of an async 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.

6.2.2 ExecuteAsyncModule ( module )

  1. Assert: module.[[Status]] is evaluating or evaluating-async.
  2. Assert: module.[[Async]] is true.
  3. Let capability be ! NewPromiseCapability(%Promise%).
  4. Let stepsFulfilled be the steps of a CallAsyncModuleFulfilled function as specified below.
  5. Let onFulfilled be CreateBuiltinFunction(stepsFulfilled, « [[Module]] »).
  6. Set onFulfilled.[[Module]] to module.
  7. Let stepsRejected be the steps of a CallAsyncModuleRejected function as specified below.
  8. Let onRejected be CreateBuiltinFunction(stepsRejected, « [[Module]] »).
  9. Set onRejected.[[Module]] to module.
  10. Perform ! PerformPromiseThen(capability.[[Promise]], onFulfilled, onRejected).
  11. Perform ! module.ExecuteModule(capability).
  12. Return.

A CallAsyncModuleFulfilled function is an anonymous built-in function with a [[Module]] internal slot. When a CallAsyncModuleFulfilled function is called that expects no arguments it performs the following steps:

  1. Let f be the active function object.
  2. Let module be f.[[Module]].
  3. Perform ! AsyncModuleExecutionFulfilled(module).
  4. Return.

A CallAsyncModuleRejected function is an anonymous built-in function with a [[Module]] internal slot. When a CallAsyncModuleRejected function is called with argument error it performs the following steps:

  1. Let f be the active function object.
  2. Let module be f.[[Module]].
  3. Perform ! AsyncModuleExecutionRejected(module, error).
  4. Return.

6.2.3 GatherAsyncParentCompletions ( module, execList )

  1. For each Module m of module.[[AsyncParentModules]], do
    1. If execList does not contain m and m.[[CycleRoot]].[[EvaluationError]] is empty, then
      1. Assert: m.[[Status]] is evaluating-async.
      2. Assert: m.[[EvaluationError]] is empty.
      3. Assert: m.[[AsyncEvaluation]] is true.
      4. Assert: m.[[PendingAsyncDependencies]] > 0.
      5. Set m.[[PendingAsyncDependencies]] to m.[[PendingAsyncDependencies]] - 1.
      6. If m.[[PendingAsyncDependencies]] is equal to 0, then
        1. Append m to execList.
        2. If m.[[Async]] is false, perform ! GatherAsyncParentCompletions(m, execList).
  2. Return undefined.
Note

When an async execution for a root module is fulfilled, this function determines the list of modules which are able to synchronously execute together on this completion, populating them in execList.

6.2.4 AsyncModuleExecutionFulfilled ( module )

  1. If module.[[Status]] is evaluated,
    1. Assert: module.[[EvaluationError]] is not empty.
    2. Return undefined.
  2. Assert: module.[[Status]] is evaluating-async.
  3. Assert: module.[[AsyncEvaluation]] is true.
  4. Assert: module.[[EvaluationError]] is empty.
  5. Set module.[[Status]] to evaluated.
  6. If module.[[TopLevelCapability]] is not empty, then
    1. Assert: module.[[CycleRoot]] is equal to module.
    2. Perform ! Call(module.[[TopLevelCapability]].[[Resolve]], undefined, «undefined»).
  7. Let execList be a new empty List.
  8. Perform ! GatherAsyncParentCompletions(module, execList).
  9. Let sortedExecList be a List of elements that are the elements of execList, in the order in which they had their [[AsyncEvaluation]] fields set to true in InnerModuleEvaluation.
  10. Assert: All elements of sortedExecList have their [[AsyncEvaluation]] field set to true, [[PendingAsyncDependencies]] field set to 0 and [[EvaluationError]] field set to undefined.
  11. For each Module m of sortedExecList, do
    1. If m.[[Status]] is evaluated, then
      1. Assert: m.[[EvaluationError]] is not empty.
    2. Otherwise, if m.[[Async]] is true, then
      1. Perform ! ExecuteAsyncModule(m).
    3. Otherwise,
      1. Let result be m.ExecuteModule().
      2. If result is an abrupt completion,
        1. Perform ! AsyncModuleExecutionRejected(m, result.[[Value]]).
      3. Otherwise,
        1. Set m.[[Status]] to evaluated.
        2. If m.[[TopLevelCapability]] is not empty, then
          1. Assert: m.[[CycleRoot]] is equal to m.
          2. Perform ! Call(m.[[TopLevelCapability]].[[Resolve]], undefined, «undefined»).
  12. Return undefined.

6.2.5 AsyncModuleExecutionRejected ( module, error )

  1. If module.[[Status]] is evaluated,
    1. Assert: module.[[EvaluationError]] is not empty.
    2. Return undefined.
  2. Assert: module.[[Status]] is evaluating-async.
  3. Assert: module.[[AsyncEvaluation]] is true.
  4. Assert: module.[[EvaluationError]] is empty.
  5. Set module.[[EvaluationError]] to ThrowCompletion(error).
  6. Set module.[[Status]] to evaluated.
  7. For each Module m of module.[[AsyncParentModules]], do
    1. Perform ! AsyncModuleExecutionRejected(m, error).
  8. If module.[[TopLevelCapability]] is not empty, then
    1. Assert: module.[[CycleRoot]] is equal to module.
    2. Perform ! Call(module.[[TopLevelCapability]].[[Reject]], undefined, «error»).
  9. Return undefined.

7 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.Link(), this will complete successfully by assumption, and recursively link modules B and C as well, such that A.[[Status]] = B.[[Status]] = C.[[Status]] = linked. This preparatory step 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. 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. 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.)

The difference here between linking and evaluation errors is due to how 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.

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. HostResolveImportedModule throws 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 throwing an exception when trying to parse the resulting source text. Hosts can choose to expose the cause of failure via the exception they throw from HostResolveImportedModule. In any case, this exception causes a linking failure, which as before results in A's [[Status]] remaining unlinked.

LastlyNow, 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.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.

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

Now consider a case where A has an 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.

Finally, 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

Linking happens as before, and all modules end up with [[Status]] set to linked.

Calling A.Evaluate() triggers 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 triggers the execution promise for 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, ExecuteAsyncModule(E) is called, which triggers its execution promise. Because E is not part of a cycle, it is immediately removed from the stack and transitions to evaluated. 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 5.

Table 5: 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 6.

Table 6: 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 its execution is triggered. C.[[PendingAsyncDependencies]] is also decremented to become 0, and C's execution is triggered. The fields of the updated modules are as given in Table 7.

Table 7: 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 8.

Table 8: 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 A's execution is triggered. The fields of the updated modules are as given in Table 13.

Table 9: 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 10.

Table 10: 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 and C.[[Status]] is set to evaluated. We then set the evaluation error of the module to the error returned by executing C. We then propagate this error to all of the AsyncParentModules by performing AsyncModuleExeutionRejected on each of them. The fields of the updated modules are as given in Table 11.

Table 11: 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 » 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 12.

Table 12: 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. GatherAsyncParentCompletions 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 13: 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

8 Source Text Module Records

8.1 ParseModule ( sourceText, realm, hostDefined )

The abstract operation ParseModule takes arguments sourceText (ECMAScript source text), realm, and hostDefined. It creates a Source Text Module Record based upon the result of parsing sourceText as a Module. It performs the following steps when called:

  1. Assert: sourceText is an ECMAScript source text (see clause 10).
  2. Parse sourceText using Module as the goal symbol and analyse the parse result for any Early Error conditions. If the parse was successful and no early errors were found, let body be the resulting parse tree. Otherwise, let body be a List of one or more SyntaxError objects representing the parsing errors and/or early errors. Parsing and early error detection may be interweaved in an implementation-dependent manner. If more than one parsing error or early error is present, the number and ordering of error objects in the list is implementation-dependent, but at least one must be present.
  3. If body is a List of errors, return body.
  4. Let requestedModules be the ModuleRequests of body.
  5. Let importEntries be ImportEntries of body.
  6. Let importedBoundNames be ImportedLocalNames(importEntries).
  7. Let indirectExportEntries be a new empty List.
  8. Let localExportEntries be a new empty List.
  9. Let starExportEntries be a new empty List.
  10. Let exportEntries be ExportEntries of body.
  11. For each ExportEntry Record ee in exportEntries, do
    1. If ee.[[ModuleRequest]] is null, then
      1. If ee.[[LocalName]] is not an element of importedBoundNames, then
        1. Append ee to localExportEntries.
      2. Else,
        1. Let ie be the element of importEntries whose [[LocalName]] is the same as ee.[[LocalName]].
        2. If ie.[[ImportName]] is "*", then
          1. NOTE: This is a re-export of an imported module namespace object.
          2. Append ee to localExportEntries.
        3. Else,
          1. NOTE: This is a re-export of a single name.
          2. Append the ExportEntry Record { [[ModuleRequest]]: ie.[[ModuleRequest]], [[ImportName]]: ie.[[ImportName]], [[LocalName]]: null, [[ExportName]]: ee.[[ExportName]] } to indirectExportEntries.
    2. Else if ee.[[ImportName]] is "*" and ee.[[ExportName]] is null, then
      1. Append ee to starExportEntries.
    3. Else,
      1. Append ee to indirectExportEntries.
  12. Let async be body Contains await.
  13. Return Source Text Module Record { [[Realm]]: realm, [[Environment]]: empty, [[Namespace]]: empty, [[CycleRoot]]: empty, [[Async]]: async, [[AsyncEvaluation]]: false, [[TopLevelCapability]]: empty, [[AsyncParentModules]]: « », [[PendingAsyncDependencies]]: empty, [[Status]]: unlinked, [[EvaluationError]]: empty, [[HostDefined]]: hostDefined, [[ECMAScriptCode]]: body, [[Context]]: empty, [[ImportMeta]]: empty, [[RequestedModules]]: requestedModules, [[ImportEntries]]: importEntries, [[LocalExportEntries]]: localExportEntries, [[IndirectExportEntries]]: indirectExportEntries, [[StarExportEntries]]: starExportEntries, [[DFSIndex]]: empty, [[DFSAncestorIndex]]: empty }.
Note

An implementation may parse module source text and analyse it for Early Error conditions prior to the evaluation of ParseModule for that module source text. However, the reporting of any errors must be deferred until the point where this specification actually performs ParseModule upon that source text.

8.2 ExecuteModule ( [ capability ] ) Concrete Method

The ExecuteModule concrete method of a Source Text Module Record implements the corresponding Cyclic Module Record abstract method.

This abstract method performs the following steps:

  1. Let module be this Source Text Module Record.
  2. Let moduleContext be a new ECMAScript code execution context.
  3. Set the Function of moduleContext to null.
  4. Set the Realm of moduleContext to module.[[Realm]].
  5. Set the ScriptOrModule of moduleContext to module.
  6. Assert: module has been linked and declarations in its module environment have been linked.
  7. Set the VariableEnvironment of moduleContext to module.[[Environment]].
  8. Set the LexicalEnvironment of moduleContext to module.[[Environment]].
  9. Suspend the currently running execution context.
  10. Let moduleContext be module.[[Context]].
  11. Push moduleContext onto the execution context stack; moduleContext is now the running execution context.
  12. Let result be the result of evaluating module.[[ECMAScriptCode]].
  13. Suspend moduleContext and remove it from the execution context stack.
  14. Resume the context that is now on the top of the execution context stack as the running execution context.
  15. Return Completion(result).
  16. If module.[[Async]] is false, then
    1. Assert: capability was not provided.
    2. Push moduleCxt on to the execution context stack; moduleCxt is now the running execution context.
    3. Let result be the result of evaluating module.[[ECMAScriptCode]].
    4. Suspend moduleCxt and remove it from the execution context stack.
    5. Resume the context that is now on the top of the execution context stack as the running execution context.
    6. Return Completion(result).
  17. Otherwise,
    1. Assert: capability is a PromiseCapability Record.
    2. Perform ! AsyncBlockStart(capability, module.[[ECMAScriptCode]], moduleCxt).
    3. Return.

9 Modules

Syntax

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

10 Async Function Definitions

Syntax

10.1 Async Function Definitions

Syntax

AsyncFunctionDeclaration[Yield, Await, Default]:async[no LineTerminator here]functionBindingIdentifier[?Yield, ?Await](FormalParameters[~Yield, +Await]){AsyncFunctionBody} [+Default]async[no LineTerminator here]function(FormalParameters[~Yield, +Await]){AsyncFunctionBody} AsyncFunctionExpression:async[no LineTerminator here]function(FormalParameters[~Yield, +Await]){AsyncFunctionBody} async[no LineTerminator here]functionBindingIdentifier[~Yield, +Await](FormalParameters[~Yield, +Await]){AsyncFunctionBody} AsyncMethod[Yield, Await]:async[no LineTerminator here]PropertyName[?Yield, ?Await](UniqueFormalParameters[~Yield, +Await]){AsyncFunctionBody} AsyncFunctionBody:FunctionBody[~Yield, +Await] AwaitExpression[Yield]:awaitUnaryExpression[?Yield, +Await] Note

await is parsed as an AwaitExpression when the [Await] parameter is present. The [Await] parameter is present in the top level of the following contexts, although these contexts may also contain nonterminals which disable the parameter, such as FunctionBody:

When Module is the syntactic goal symbol and the [Await] parameter is absent, await is parsed as a keyword and will be a Syntax error. When Script is the syntactic goal symbol, await may be parsed as an identifier when the [Await] parameter is absent. This includes the following contexts:

10.2 Imports

Syntax

ImportedBinding:BindingIdentifier[~Yield, ~Await] BindingIdentifier[~Yield, +Await]

10.3 Exports

Syntax

ExportDeclaration:export*FromClause; exportExportClauseFromClause; exportExportClause; exportVariableStatement[~Yield, ~Await] exportVariableStatement[~Yield, +Await] exportDeclaration[~Yield, ~Await] exportDeclaration[~Yield, +Await] exportdefaultHoistableDeclaration[~Yield, ~Await, +Default] exportdefaultHoistableDeclaration[~Yield, +Await, +Default] exportdefaultClassDeclaration[~Yield, ~Await, +Default] exportdefaultClassDeclaration[~Yield, +Await, +Default] exportdefault[lookahead ∉ { function, async [no LineTerminator here] function, class }]AssignmentExpression[+In, ~Yield, ~Await]; exportdefault[lookahead ∉ { function, async [no LineTerminator here] function, class }]AssignmentExpression[+In, ~Yield, +Await];