Stage 3 Draft / November 22, 2019

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.

1AsyncFunctionStart ( promiseCapability, asyncFunctionBody )

  1. Let runningContext be the running execution context.
  2. Let asyncContext be a copy of runningContext.
  3. Perform ! AsyncBlockStart(promiseCapability, asyncFunctionBody, asyncContext).
  4. 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.
  5. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
  6. Resume the suspended evaluation of asyncContext. Let result be the value returned by the resumed computation.
  7. Assert: When we return here, asyncContext has already been removed from the execution context stack and runningContext is the currently running execution context.
  8. 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 3.g above.
  9. Return.

2AsyncBlockStart ( 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.

3Abstract Module Records

Table 1: Module Record Fields
Field Name Value Type Meaning
[[Realm]] Realm Record | undefined The Realm within which this module was created. undefined if not yet assigned.
[[Environment]] Lexical Environment | undefined The Lexical Environment containing the top level bindings for this module. This field is set when the module is linked.
[[Namespace]] Object | undefined The Module Namespace Object (26.3) if one has been created for this module. Otherwise undefined.
[[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 }. Return null if the name cannot be resolved, or "ambiguous" if multiple bindings were found.

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

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.

Link must have completed successfully prior to invoking this method.

4Cyclic 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.

Table 3: Additional Fields of Cyclic Module Records
Field Name Value Type Meaning
[[Status]] String Initially "unlinked". Transitions to "linking", "linked", "evaluating", "evaluated" (in that order) as the module progresses throughout its lifecycle.
[[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 | undefined Auxiliary field used during Link and Evaluate only. If [[Status]] is "linking" or, "evaluating", or "evaluated", this nonnegative number records the point at which the module was first visited during the depth-first traversal of the dependency graph.
[[DFSAncestorIndex]] Integer | undefined Auxiliary field used during Link and Evaluate only. If [[Status]] is "linking" or, "evaluating", or "evaluated", 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.
[[Async]] true or false 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.
[[AsyncEvaluating]] true or false Whether this module is currently awaiting async fulfillment.
[[TopLevelCapability]] Promise Capability | undefined 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 undefined for any dependencies of that module, unless a top-level Evaluate() has been initiated for any of those dependencies.
[[AsyncParentModules]] List of Cyclic Module Record | undefined 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 | undefined 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, which will only happen if there are no dependency 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 Lexical Environment of the module, including resolving all imported bindings.
ExecuteModule( [ promiseCapability ] ) Initialize the execution context of the module and evaluate the module's code within it. 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.

4.1Link ( ) 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".
      3. Set m.[[Environment]] to undefined.
      4. Set m.[[DFSIndex]] to undefined.
      5. Set m.[[DFSAncestorIndex]] to undefined.
    2. Assert: module.[[Status]] is "unlinked".
    3. Return result.
  6. Assert: module.[[Status]] is "linked" or "evaluated".
  7. Assert: stack is empty.
  8. Return undefined.

4.1.1InnerModuleLinking ( module, stack, index )

The InnerModuleLinking abstract operation is used by Link to perform the actual linking process for the Cyclic Module Record 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.

This abstract operation performs the following steps:

  1. If module is not a Cyclic Module Record, then
    1. Perform ? module.Link().
    2. Return index.
  2. If module.[[Status]] is "linking", "linked", 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. Increase index by 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", 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.

4.2Evaluate ( ) 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 "evaluated".

If execution results in a 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. Let module be this Cyclic Module Record.
  2. Assert: module.[[Status]] is "linked" or "evaluated".
  3. If module.[[Status]] is "evaluated", set module to GetAsyncCycleRoot(module).
  4. If module.[[TopLevelCapability]] is not undefined, then
    1. Return module.[[TopLevelCapability]].[[Promise]].
  5. Let stack be a new empty List.
  6. Let capability be ! NewPromiseCapability(%Promise%).
  7. Set module.[[TopLevelCapability]] to capability.
  8. Let result be InnerModuleEvaluation(module, stack, 0).
  9. 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]]»).
  10. Otherwise,
    1. Assert: module.[[Status]] is "evaluated" and module.[[EvaluationError]] is undefined.
    2. If module.[[AsyncEvaluating]] is false, then
      1. Perform ! Call(capability.[[Resolve]], undefined, «undefined»).
    3. Assert: stack is empty.
  11. Return undefinedcapability.[[Promise]].

4.2.1InnerModuleEvaluation( module, stack, index )

The InnerModuleEvaluation abstract operation is used by Evaluate to perform the actual evaluation process for the Cyclic Module Record 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 [[DFSAncestoreIndex]] fields, are used the same way as in InnerModuleLinking.

This abstract operation performs the following steps:

  1. If module is not a Cyclic Module Record, then
    1. Perform ? module.Evaluate().
    2. Return index.
  2. If module.[[Status]] is "evaluated", then
    1. If module.[[EvaluationError]] is undefined, 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 module.[[AsyncParentModules]] to a new empty List.
  10. Set index to index + 1.
  11. Append module to stack.
  12. 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" 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 GetAsyncCycleRoot(requiredModule).
        2. Assert: requiredModule.[[Status]] is "evaluated".
        3. If requiredModule.[[EvaluationError]] is not undefined, return module.[[EvaluationError]].
      5. If requiredModule.[[AsyncEvaluating]] is true, then
        1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1.
        2. Append module to requiredModule.[[AsyncParentModules]].
  13. Perform ? module.ExecuteModule().
  14. If module.[[PendingAsyncDependencies]] is > 0, set module.[[AsyncEvaluating]] to true.
  15. Otherwise, if module.[[Async]] is true, perform ! ExecuteAsyncModule(module).
  16. Otherwise, perform ? module.ExecuteModule().
  17. Assert: module occurs exactly once in stack.
  18. Assert: module.[[DFSAncestorIndex]] is less than or equal to module.[[DFSIndex]].
  19. 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 "evaluated".
      5. If requiredModule and module are the same Module Record, set done to true.
  20. Return index.
Note 1

A module is "evaluating" while it is being traversed by InnerModuleEvaluation. A module is "evaluated" on execution completion and 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 GetAsyncCycleRoot. This ensures that the cycle state can be treated as a single strongly connected component through its root module state.

4.2.2ExecuteAsyncModule ( module )

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

4.2.3GetAsyncCycleRoot ( module )

  1. Assert: module.[[Status]] is "evaluated".
  2. If module.[[AsyncParentModules]] is an empty List, return module.
  3. Repeat, while module.[[DFSIndex]] is greater than module.[[DFSAncestorIndex]],
    1. Assert: module.[[AsyncParentModules]] is a non-empty List.
    2. Let nextCycleModule be the first element of module.[[AsyncParentModules]].
    3. Assert: nextCycleModule.[[DFSAncestorIndex]] is less than or equal to module.[[DFSAncestorIndex]].
    4. Set module to nextCycleModule.
  4. Assert: module.[[DFSIndex]] is equal to module.[[DFSAncestorIndex]].
  5. Return module.

4.2.4AsyncModuleExecutionFulfilled ( module )

  1. Assert: module.[[Status]] is "evaluated".
  2. If module.[[AsyncEvaluating]] is false,
    1. Assert: module.[[EvaluationError]] is not undefined.
    2. Return undefined.
  3. Assert: module.[[EvaluationError]] is undefined.
  4. Set module.[[AsyncEvaluating]] to false.
  5. For each Module m of module.[[AsyncParentModules]], do
    1. If module.[[DFSIndex]] is not equal to module.[[DFSAncestorIndex]], then
      1. Assert: m.[[DFSAncestorIndex]] is less than or equal to module.[[DFSAncestorIndex]].
    2. Decrement m.[[PendingAsyncDependencies]] by 1.
    3. If m.[[PendingAsyncDependencies]] is 0 and m.[[EvaluationError]] is undefined, then
      1. Assert: m.[[AsyncEvaluating]] is true.
      2. Let cycleRoot be ! GetAsyncCycleRoot(m).
      3. If cycleRoot.[[EvaluationError]] is not undefined, return undefined.
      4. If m.[[Async]] is true, then
        1. Perform ! ExecuteAsyncModule(m).
      5. Otherwise,
        1. Let result be m.ExecuteModule().
        2. If result is a normal completion,
          1. Perform ! AsyncModuleExecutionFulfilled(m).
        3. Otherwise,
          1. Perform ! AsyncModuleExecutionRejected(m, result.[[Value]]).
  6. If module.[[TopLevelCapability]] is not undefined, then
    1. Assert: module.[[DFSIndex]] is equal to module.[[DFSAncestorIndex]].
    2. Perform ! Call(module.[[TopLevelCapability]].[[Resolve]], undefined, «undefined»).
  7. Return undefined.

4.2.5AsyncModuleExecutionRejected ( module, error )

  1. Assert: module.[[Status]] is "evaluated".
  2. If module.[[AsyncEvaluating]] is false,
    1. Assert: module.[[EvaluationError]] is not undefined.
    2. Return undefined.
  3. Assert: module.[[EvaluationError]] is undefined.
  4. Set module.[[EvaluationError]] to ThrowCompletion(error).
  5. Set module.[[AsyncEvaluating]] to false.
  6. For each Module m of module.[[AsyncParentModules]], do
    1. If module.[[DFSIndex]] is not equal to module.[[DFSAncestorIndex]], then
      1. Assert: m.[[DFSAncestorIndex]] is equal to module.[[DFSAncestorIndex]].
    2. Perform ! AsyncModuleExecutionRejected(m, error).
  7. If module.[[TopLevelCapability]] is not undefined, then
    1. Assert: module.[[DFSIndex]] is equal to module.[[DFSAncestorIndex]].
    2. Perform ! Call(module.[[TopLevelCapability]].[[Reject]], undefined, «error»).
  8. Return undefined.

5Source Text Module Records

5.1ParseModule ( sourceText, realm, hostDefined )

The abstract operation ParseModule with arguments sourceText, realm, and hostDefined creates a Source Text Module Record based upon the result of parsing sourceText as a Module. ParseModule performs the following steps:

  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 or ReferenceError 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. Assert: This is a re-export of an imported module namespace object.
          2. Append ee to localExportEntries.
        3. Else this is a re-export of a single name,
          1. Append the ExportEntry Record { [[ModuleRequest]]: ie.[[ModuleRequest]], [[ImportName]]: ie.[[ImportName]], [[LocalName]]: null, [[ExportName]]: ee.[[ExportName]] } to indirectExportEntries.
    2. Else if ee.[[ImportName]] is "*", 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]]: undefined, [[Namespace]]: undefined, [[Async]]: async, [[AsyncEvaluating]]: false, [[TopLevelCapability]]: undefined, [[AsyncParentModules]]: undefined, [[PendingAsyncDependencies]]: undefined, [[Status]]: "unlinked", [[EvaluationError]]: undefined, [[HostDefined]]: hostDefined, [[ECMAScriptCode]]: body, [[RequestedModules]]: requestedModules, [[ImportEntries]]: importEntries, [[LocalExportEntries]]: localExportEntries, [[IndirectExportEntries]]: indirectExportEntries, [[StarExportEntries]]: starExportEntries, [[DFSIndex]]: undefined, [[DFSAncestorIndex]]: undefined }.
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.

5.2ExecuteModule ( [ capability ] )

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 moduleCxt be a new ECMAScript code execution context.
  3. Set the Function of moduleCxt to null.
  4. Assert: module.[[Realm]] is not undefined.
  5. Set the Realm of moduleCxt to module.[[Realm]].
  6. Set the ScriptOrModule of moduleCxt to module.
  7. Assert: module has been linked and declarations in its module environment have been linked.
  8. Set the VariableEnvironment of moduleCxt to module.[[Environment]].
  9. Set the LexicalEnvironment of moduleCxt to module.[[Environment]].
  10. Suspend the currently running execution context.
  11. 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).
  12. Otherwise,
    1. Assert: capability is a PromiseCapability Record.
    2. Perform ! AsyncBlockStart(capability, module.[[ECMAScriptCode]], moduleCxt).
    3. Return.

6Example Source Text 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

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 Source Text 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 an linking failure, which as before results in A's [[Status]] remaining "unlinked".

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.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 ModuleDeclarationEnvironmentSetup, 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, which sets D.[[AsyncEvaluating]] to true and executes D (up until the first await). We unwind back to the original InnerModuleEvaluation on A, setting B.[[AsyncEvaluating]] to true. 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 sets E.[[AsyncEvaluating]] to true and starts executing. 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.[[AsyncEvaluating]] to true. Now we finish the loop over A's dependencies, set A.[[AsyncEvaluating]] to true, and remove the entire strongly connected component from the stack, transitioning all of the modules to "evaluated" 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]] [[AsyncEvaluating]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A 0 0 true « » 2 (B and C)
B 1 0 true « A » 1 (D)
C 2 0 true « A » 2 (D and E)
D 3 0 true « B, C » 0
E 4 4 true « C » 0

Let us assume that E finishes executing first. When that happens, AsyncModuleExecutionFulfilled is called, E.[[AsyncEvaluating]] is set to false 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]] [[AsyncEvaluating]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
C 2 0 true « A » 1 (D)
E 4 4 false « 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.[[AsyncEvaluating]] is set to false. Then B.[[PendingAsyncDependencies]] is decremented to become 0, ExecuteAsyncModule is called on B, and it starts executing. Once the synchronous part of B's execution has finished, C.[[PendingAsyncDependencies]] is also decremented to become 0, and C starts executing. The fields of the updated modules are as given in Table 7.

Table 7: Module fields after module D finishes executing
Module [[DFSIndex]] [[DFSAncestorIndex]] [[AsyncEvaluating]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
B 1 0 true « A » 0
C 2 0 true « A » 0
D 3 0 false « B, C » 0

Let us assume that C finishes executing next. When that happens, AsyncModuleExecutionFulfilled is called again, C.[[AsyncEvaluating]] is set to false 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]] [[AsyncEvaluating]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A 0 0 true « » 1 (B)
C 2 0 false « A » 0

Then, B finishes executing. When that happens, AsyncModuleExecutionFulfilled is called again and B.[[AsyncEvaluating]] is set to false. 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 9.

Table 9: Module fields after module B finishes executing
Module [[DFSIndex]] [[DFSAncestorIndex]] [[AsyncEvaluating]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A 0 0 true « » 0
B 1 0 false « A » 0

Finally, A finishes executing. When that happens, AsyncModuleExecutionFulfilled is called again and A.[[AsyncEvaluating]] is set to false. 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]] [[AsyncEvaluating]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A 0 0 false « » 0

7Runtime Semantics: TopLevelModuleEvaluationJob ( sourceText, hostDefined )

A TopLevelModuleEvaluationJob with parameters sourceText and hostDefined is a job that parses, validates, and evaluates sourceText as a Module.

  1. Assert: sourceText is an ECMAScript source text (see clause 10).
  2. Let realm be the current Realm Record.
  3. Let m be ParseModule(sourceText, realm, hostDefined).
  4. If m is a List of errors, then
    1. Perform HostReportErrors(m).
    2. Return NormalCompletion(undefined).
  5. Perform ? m.Link().
  6. Assert: All dependencies of m have been transitively resolved and m is ready for evaluation.
  7. ReturnLet promise be ? m.Evaluate().
  8. Let stepsRejected be the steps of a ReportRejectedError function as specified below.
  9. Let onRejected be CreateBuiltinFunction(stepsRejected).
  10. Perform ! PerformPromiseThen(promise, undefined, onRejected).
  11. Return.

A ReportRejectedError function is an anonymous built-in function. When a ReportRejectedError function is called with argument reason it performs the following steps:

  1. Perform HostReportErrorsreason »).
  2. Return.
Note

An implementation may parse a sourceText as a Module, analyse it for Early Error conditions, and link it prior to the execution of the TopLevelModuleEvaluationJob for that sourceText. An implementation may also resolve, pre-parse and pre-analyse, and pre-link module dependencies of sourceText. However, the reporting of any errors detected by these actions must be deferred until the TopLevelModuleEvaluationJob is actually executed.

8Modules

Syntax

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

9Async Function Definitions

Syntax

9.1Async 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:

9.2Imports

Syntax

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

9.3Exports

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];