diff --git a/README.md b/README.md index d515cb9..d3287db 100644 --- a/README.md +++ b/README.md @@ -272,9 +272,9 @@ Currently (in a world without top-level `await`), polyfills are synchronous. So, #### Does the `Promise.all` happen even if none of the imported modules have a top-level `await`? -Yes. In particular, if none of the imported modules have a top-level `await`, there will still be a delay of some turns on the Promise job queue until the module body executes. The goal here is to avoid too much synchronous behavior, which would break if something turns out to be asynchronous in the future, or even alternate between those two depending on runtime conditions ("releasing Zalgo"). Similar considerations led to the decision that `await` should always be asynchronous, even if passed a non-Promise. +If the module's execution is deterministically synchronous (that is, if it and its dependencies each contain no top-level `await`), there will be no entry in the `Promise.all` for that module. In this case, it will run synchronously. -Note, this is an observable change from current ES Module semantics, where the Evaluate phase is entirely synchronous. For a concrete example and further discussion, see [issue #43](https://github.com/tc39/proposal-top-level-await/issues/43) and [#47](https://github.com/tc39/proposal-top-level-await/issues/47). +These semantics preserve the current behavior of ES Modules, where, when top-level `await` is not used, the Evaluate phase is entirely synchronous. The semantics are a bit in contrast with uses of Promises elsewhere. For a concrete example and further discussion, see [issue #43](https://github.com/tc39/proposal-top-level-await/issues/43) and [#47](https://github.com/tc39/proposal-top-level-await/issues/47). #### Does top-level `await` increase the risk of deadlocks? diff --git a/spec.html b/spec.html index 52f402b..30cdb3b 100644 --- a/spec.html +++ b/spec.html @@ -130,18 +130,34 @@

Abstract Module Records

- [[ExecPromise]] + [[Async]] - Promise | *undefined* + *true* or *false* - The evaluation promise for this Abstract Module Record, including any dependency evaluations. + Indicates whether this module is asynchronous. Abstract Module Record subclasses must not modify this field after the Instantiation phase. + + + + + [[EvaluationPromise]] + + + *undefined* | Promise + + + If [[Async]] is true, and evaluation of this module has begun, this field stores a Promise that resolves or rejects when evaluation of the module is complete. + + +

For all module types defined in this specification, it's statically determined (given the set of parsed modules and the graph connecting them) whether a module will have an [[EvaluationPromise]] which is not *undefined* after Evaluate() is called. A Promise is only present if the module or a dependency has the [[Async]] flag set. As more module types are added, the intention is that this would remain the case. However, concepts like "dependency" are not defined on Abstract Module Records.

+
+ @@ -183,7 +199,7 @@

Abstract Module Records

Evaluate() @@ -222,13 +238,13 @@

Cyclic Module Records

@@ -265,6 +281,17 @@

Cyclic Module Records

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. + + + + +
-

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. Return a Promise representing the evaluation state of the module. If the module has been evaluated successfully, return a Promise resolved with *undefined*. If the module was evaluated unsuccessfully, return a Promise rejected with the error. If the evaluation of the module has not yet completed (e.g., due to a top-level await), return a Promise which is not settled, and which may settle into one of the previous states when appropriate. If this method is called multiple times, return the previous Promise rather than re-evaluating the module.

+

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.

Instantiate must have completed successfully prior to invoking this method.

- [[EvaluationError]] + [[EvaluationError]] - An abrupt completion | *undefined* + 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"`. + A completion of type ~throw~ representing the exception that occurred during evaluation. *undefined* if no exception occurred, if the module is [[Async]], or if [[Status]] is not `"evaluated"`.
+ [[ModuleAsync]] + + *true* or *false* + + Whether this module is individually asynchronous. A module may have [[Async]] *true* and [[ModuleAsync]] *false* if dependencies are asynchronous, but this module is not. This field must not change after the module is parsed. +
@@ -290,22 +317,96 @@

Cyclic Module Records

- ExecuteModule( _promiseCapability_ ) + ExecuteModule( [ _promiseCapability_ ] ) - Initialize the execution context of the module and evaluate the module's code within it. Resolve or reject the given Promise capability when module evaluation completes. + Initialize the execution context of the module and evaluate the module's code within it. If this module has *true* in [[ModuleAsync]], 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. - - - + + + + + +

Instantiate ( ) Concrete Method

+ +

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

+

On success, Instantiate transitions this module's [[Status]] from `"uninstantiated"` to `"instantiated"`. On failure, an exception is thrown and this module's [[Status]] remains `"uninstantiated"`.

+ +

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

+ + + 1. Let _module_ be this Cyclic Module Record. + 1. Assert: _module_.[[Status]] is not `"instantiating"` or `"evaluating"`. + 1. Let _stack_ be a new empty List. + 1. Let _result_ be InnerModuleInstantiation(_module_, _stack_, 0). + 1. If _result_ is an abrupt completion, then + 1. For each module _m_ in _stack_, do + 1. Assert: _m_.[[Status]] is `"instantiating"`. + 1. Set _m_.[[Status]] to `"uninstantiated"`. + 1. Set _m_.[[Environment]] to *undefined*. + 1. Set _m_.[[DFSIndex]] to *undefined*. + 1. Set _m_.[[DFSAncestorIndex]] to *undefined*. + 1. Assert: _module_.[[Status]] is `"uninstantiated"`. + 1. Return _result_. + 1. Assert: _module_.[[Status]] is `"instantiated"` or `"evaluated"`. + 1. Assert: _stack_ is empty. + 1. Return *undefined*. + + + +

InnerModuleInstantiation ( _module_, _stack_, _index_ )

+ +

The InnerModuleInstantiation abstract operation is used by Instantiate to perform the actual instantiation 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 `"instantiated"` together.

+ +

This abstract operation performs the following steps:

+ + + 1. If _module_ is not a Cyclic Module Record, then + 1. Perform ? _module_.Instantiate(). + 1. Return _index_. + 1. If _module_.[[Status]] is `"instantiating"`, `"instantiated"`, or `"evaluated"`, then + 1. Return _index_. + 1. Assert: _module_.[[Status]] is `"uninstantiated"`. + 1. Set _module_.[[Status]] to `"instantiating"`. + 1. Set _module_.[[DFSIndex]] to _index_. + 1. Set _module_.[[DFSAncestorIndex]] to _index_. + 1. Increase _index_ by 1. + 1. Append _module_ to _stack_. + 1. For each String _required_ that is an element of _module_.[[RequestedModules]], do + 1. Let _requiredModule_ be ? HostResolveImportedModule(_module_, _required_). + 1. Set _index_ to ? InnerModuleInstantiation(_requiredModule_, _stack_, _index_). + 1. Assert: _requiredModule_.[[Status]] is either `"instantiating"`, `"instantiated"`, or `"evaluated"`. + 1. Assert: _requiredModule_.[[Status]] is `"instantiating"` if and only if _requiredModule_ is in _stack_. + 1. If _requiredModule_.[[Status]] is `"instantiating"`, then + 1. Assert: _requiredModule_ is a Cyclic Module Record. + 1. Set _module_.[[DFSAncestorIndex]] to min(_module_.[[DFSAncestorIndex]], _requiredModule_.[[DFSAncestorIndex]]). + 1. If _requiredModule_.[[Async]] is *true*, then + 1. Set _module_.[[Async]] to *true*. + 1. Perform ? _module_.InitializeEnvironment(). + 1. Assert: _module_ occurs exactly once in _stack_. + 1. Assert: _module_.[[DFSAncestorIndex]] is less than or equal to _module_.[[DFSIndex]]. + 1. If _module_.[[DFSAncestorIndex]] equals _module_.[[DFSIndex]], then + 1. Let _done_ be *false*. + 1. Repeat, while _done_ is *false*, + 1. Let _requiredModule_ be the last element in _stack_. + 1. Remove the last element of _stack_. + 1. Set _requiredModule_.[[Status]] to `"instantiated"`. + 1. If _requiredModule_ and _module_ are the same Module Record, set _done_ to *true*. + 1. Return _index_. + +
+
+

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 `"instantiated"` to `"evaluated"`, at which point the [[ExecPromise]] Promise field is populated to a promise resolving on completion of the module execution, including its dependency executions, or the associated execution error..

+

Evaluate transitions this module's [[Status]] from `"instantiated"` to `"evaluated"`.

+ +

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

-

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

+

If the module is [[Async]], then

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

@@ -313,21 +414,19 @@

Evaluate ( ) Concrete Method

1. Let _module_ be this Source Text Module Record. 1. Assert: _module_.[[Status]] is `"instantiated"` or `"evaluated"`. 1. Let _stack_ be a new empty List. - 1. Let _result_ be Perform ! InnerModuleEvaluation(_module_, _stack_, 0). - 1. Assert: _stack_ is empty. - 1. Assert: _module_.[[Status]] is `"evaluated"`. - 1. Return _module_.[[ExecPromise]]. - 1. If _result_ is an abrupt completion, then - 1. For each module _m_ in _stack_, do - 1. Assert: _m_.[[Status]] is `"evaluating"`. - 1. Set _m_.[[Status]] to `"evaluated"`. - 1. Set _m_.[[EvaluationError]] to _result_. - 1. Assert: _module_.[[Status]] is `"evaluated"` and _module_.[[EvaluationError]] is _result_. - 1. Return _result_. - 1. Assert: _result_ is *undefined*. - 1. Assert: _module_.[[Status]] is `"evaluated"` and _module_.[[EvaluationError]] is *undefined*. - 1. Assert: _stack_ is empty. - 1. Return *undefined*. + 1. Let _result_ be InnerModuleEvaluation(_module_, _stack_, 0). + 1. If _result_ is an abrupt completion, then + 1. For each module _m_ in _stack_, do + 1. Assert: _m_.[[Status]] is `"evaluating"`. + 1. If _m_.[[Async]] is *true*, return _result_. + 1. Set _m_.[[Status]] to `"evaluated"`. + 1. Set _m_.[[EvaluationError]] to _result_. + 1. Assert: _module_.[[Status]] is `"evaluated"` and _module_.[[EvaluationError]] is _result_. + 1. Return _result_. + 1. Assert: _module_.[[Status]] is `"evaluated"` and _module_.[[EvaluationError]] is *undefined*. + 1. Assert: _stack_ is empty. + 1. If _module_.[[Async]] is *true*, return _module_.[[EvaluationPromise]]. + 1. Otherwise, return *undefined*. @@ -339,35 +438,37 @@

InnerModuleEvaluation( _module_, _stack_, _index_ )

1. If _module_ is not a Source Text Module Record, then - 1. Set _module_.[[ExecPromise]] to _module_.Evaluate(). + 1. Perform _module_.Evaluate(). 1. Return _index_. 1. If _module_.[[Status]] is `"evaluated"`, then - 1. Return _index_. - 1. If _module_.[[EvaluationError]] is *undefined*, return _index_. - 1. Otherwise return _module_.[[EvaluationError]]. + 1. If _module_.[[EvaluationError]] is *undefined*, return _index_. + 1. Otherwise return _module_.[[EvaluationError]]. 1. If _module_.[[Status]] is `"evaluating"`, return _index_. 1. Assert: _module_.[[Status]] is `"instantiated"`. 1. Set _module_.[[Status]] to `"evaluating"`. 1. Set _module_.[[DFSIndex]] to _index_. 1. Set _module_.[[DFSAncestorIndex]] to _index_. - 1. Let _evalCapability_ be ! NewPromiseCapability(%Promise%). - 1. Set _module_.[[ExecPromise]] to _evalCapability_.[[Promise]]. 1. Set _index_ to _index_ + 1. 1. Append _module_ to _stack_. - 1. Let _dependencyExecPromises_ be an empty List. + 1. Let _asyncDependencies_ be an empty List. 1. For each String _required_ that is an element of _module_.[[RequestedModules]], do 1. Let _requiredModule_ be ! HostResolveImportedModule(_module_, _required_). 1. NOTE: Instantiate must be completed successfully prior to invoking this method, so every requested module is guaranteed to resolve successfully. 1. Set _index_ to ? InnerModuleEvaluation(_requiredModule_, _stack_, _index_). 1. Assert: _requiredModule_.[[Status]] is either `"evaluating"` or `"evaluated"`. 1. Assert: _requiredModule_.[[Status]] is `"evaluating"` if and only if _requiredModule_ is in _stack_. - 1. If _requiredModule_.[[Status]] is `"evaluated"`, then - 1. Add _requiredModule_.[[ExecPromise]] to the list _dependencyExecPromises_. + 1. If _requiredModule_.[[Status]] is `"evaluated"`, and _requiredModule_.[[Async]] is *true*, then + 1. Assert: _module_.[[Async]] is *true*. + 1. Append _requiredModule_.[[EvaluationPromise]] to _asyncDependencies_. 1. If _requiredModule_.[[Status]] is `"evaluating"`, then - 1. Assert: _requiredModule_ is a Source Text Module Record. + 1. Assert: _requiredModule_ is a Cyclic Module Record. 1. Set _module_.[[DFSAncestorIndex]] to min(_module_.[[DFSAncestorIndex]], _requiredModule_.[[DFSAncestorIndex]]). - 1. Perform ? ModuleExecution(_module_). - 1. Perform ! ExecuteModuleWhenImportsReady(_module_, _dependencyExecPromises_, _evalCapability_). + 1. If _module_.[[Async]] is *false*, then + 1. Perform ? _module_.ExecuteModule() + 1. Otherwise, + 1. Let _capability_ be ! NewPromiseCapability(%Promise%). + 1. Set _module_.[[EvaluationPromise]] to _capability_.[[Promise]]. + 1. Perform ! ExecuteModuleWhenImportsReady(_module_, _asyncDependencies_, _capability_). 1. Assert: _module_ occurs exactly once in _stack_. 1. Assert: _module_.[[DFSAncestorIndex]] is less than or equal to _module_.[[DFSIndex]]. 1. If _module_.[[DFSAncestorIndex]] equals _module_.[[DFSIndex]], then @@ -377,7 +478,8 @@

InnerModuleEvaluation( _module_, _stack_, _index_ )

1. Remove the last element of _stack_. 1. Set _requiredModule_.[[Status]] to `"evaluated"`. 1. If _requiredModule_ and _module_ are the same Module Record, set _done_ to *true*. - 1. Otherwise, set _requiredModule_.[[ExecPromise]] to _module_.[[ExecPromise]]. + 1. Otherwise, if _module_.[[Async]] is *true*, + 1. Set _requiredModule_.[[EvaluationPromise]] to _module_.[[EvaluationPromise]]. 1. Return _index_.
@@ -386,10 +488,11 @@

InnerModuleEvaluation( _module_, _stack_, _index_ )

-

ExecuteModuleWhenImportsReady( _module_, _promises_, _capability_ )

+

ExecuteModuleWhenImportsReady( _module_, asyncDependencies_, _capability_ )

1. If _promises_ is an empty List, - 1. Perform ! ModuleExecution(_module_, _capability_). + 1. Assert: _module_.[[ModuleAsync]] is *true*. + 1. Perform ! _module_.ExecuteModule(_capability_). 1. Return. 1. Let _index_ be 0. 1. Let _fullfilledCount_ be 0. @@ -399,7 +502,14 @@

ExecuteModuleWhenImportsReady( _module_, _promises_, _capability_ )ParseModule ( _sourceText_, _realm_, _hostDefined_ )

1. Append _ee_ to _starExportEntries_. 1. Else, 1. Append _ee_ to _indirectExportEntries_. - 1. Return Source Text Module Record { [[Realm]]: _realm_, [[Environment]]: *undefined*, [[Namespace]]: *undefined*, [[ExecPromise]]: *undefined*, [[Status]]: `"uninstantiated"`, [[EvaluationError]]: *undefined*, [[HostDefined]]: _hostDefined_, [[ECMAScriptCode]]: _body_, [[RequestedModules]]: _requestedModules_, [[ImportEntries]]: _importEntries_, [[LocalExportEntries]]: _localExportEntries_, [[IndirectExportEntries]]: _indirectExportEntries_, [[StarExportEntries]]: _starExportEntries_, [[DFSIndex]]: *undefined*, [[DFSAncestorIndex]]: *undefined* }. + 1. Let _async_ be _body_ Contains |AwaitExpression|. + 1. Return Source Text Module Record { [[Realm]]: _realm_, [[Environment]]: *undefined*, [[Namespace]]: *undefined*, [[EvaluationPromise]]: *undefined*, [[Status]]: `"uninstantiated"`, [[EvaluationError]]: *undefined*, [[HostDefined]]: _hostDefined_, [[ECMAScriptCode]]: _body_, [[RequestedModules]]: _requestedModules_, [[ImportEntries]]: _importEntries_, [[LocalExportEntries]]: _localExportEntries_, [[IndirectExportEntries]]: _indirectExportEntries_, [[StarExportEntries]]: _starExportEntries_, [[DFSIndex]]: *undefined*, [[DFSAncestorIndex]]: *undefined*, [[Async]]: _async_, [[ModuleAsync]]: _async_ }.

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.

@@ -451,7 +562,7 @@

ParseModule ( _sourceText_, _realm_, _hostDefined_ )

-

ExecuteModule ( _capability_)

+

ExecuteModule ( [ _capability_ ] )

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

@@ -464,14 +575,18 @@

ExecuteModule ( _capability_)

1. Assert: _module_ has been linked and declarations in its module environment have been instantiated. 1. Set the VariableEnvironment of _moduleCxt_ to _module_.[[Environment]]. 1. Set the LexicalEnvironment of _moduleCxt_ to _module_.[[Environment]]. - 1. Suspend the currently running execution context. - 1. Push _moduleCxt_ on to the execution context stack; _moduleCxt_ is now the running execution context. - 1. Let _result_ be the result of evaluating _module_.[[ECMAScriptCode]]. - 1. Suspend _moduleCxt_ and remove it from the execution context stack. - 1. Resume the context that is now on the top of the execution context stack as the running execution context. - 1. Return Completion(_result_). - 1. Perform ! AsyncBlockStart(_capability_, _module_.[[ECMAScriptCode]], _moduleCxt_). - 1. Return. + 1. Suspend the currently running execution context. + 1. If _module_.[[ModuleAsync]] is *false*, then + 1. Assert: _capability_ was not provided. + 1. Push _moduleCxt_ on to the execution context stack; _moduleCxt_ is now the running execution context. + 1. Let _result_ be the result of evaluating _module_.[[ECMAScriptCode]]. + 1. Suspend _moduleCxt_ and remove it from the execution context stack. + 1. Resume the context that is now on the top of the execution context stack as the running execution context. + 1. Return Completion(_result_). + 1. Otherwise, + 1. Assert: _capability_ was provided. + 1. Perform ! AsyncBlockStart(_capability_, _module_.[[ECMAScriptCode]], _moduleCxt_). + 1. Return.
@@ -491,7 +606,7 @@

Example Source Text Module Record Graphs

Consider then cases involving instantiation errors. If InnerModuleInstantiation of _C_ succeeds but, thereafter, fails for _B_, for example because it imports something that _C_ does not provide, then the original _A_.Instantiate() will fail, and both _A_ and _B_'s [[Status]] remain `"uninstantiated"`. _C_'s [[Status]] has become `"instantiated"`, 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 Promise resolving to *undefined*. The resulting exception will be recorded in both _A_ and _B_'s [[EvaluationError]][[ExecPromise]] fields as a rejected Promise, and their [[Status]] will become `"evaluated"`. _C_ will also become `"evaluated"` but, in contrast to _A_ and _B_, will remain without an [[EvaluationError]]have its [[ExecPromise]] internal slot set to a Promise resolved to *undefined*, as it successfully completed evaluation. Storing the exception in [[ExecPromise]] as a Promse rejection 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.)

+

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 Promise resolving to *undefined*. The resulting exception will be recorded in both _A_ and _B_'s [[EvaluationError]][[EvaluationResult]] fields, and their [[Status]] will become `"evaluated"`. _C_ will also become `"evaluated"` but, in contrast to _A_ and _B_, will remain without an [[EvaluationError]]have its [[EvaluationResult]] internal slot set to *undefined*, as it successfully completed evaluation. Storing the exception in [[EvaluationResult]] 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 instantiation 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.) Instantiation, 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.

@@ -515,7 +630,7 @@

Example Source Text Module Record Graphs

Now consider a case where _A_ has an instantiation 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 InnerModuleInstantiation on _A_. However, once we unwind back to the original InnerModuleInstantiation on _A_, it fails during ModuleDeclarationEnvironmentSetup, namely right after _C_.ResolveExport(). The thrown *SyntaxError* exception propagates up to _A_.Instantiate, which resets all modules that are currently on its _stack_ (these are always exactly the modules that are still `"instantiating"`). Hence both _A_ and _B_ become `"uninstantiated"`. Note that _C_ is left as `"instantiated"`.

-

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"`)via Promise rejections, which form a chain through the whole dependency graph due to ExecuteModuleWhenImportsReady. Hence both _A_ and _B_ become `"evaluated"` and the exception is recorded in both _A_ and _B_'s [[EvaluationError]][[ExecPromise]] fields, while _C_ is left as `"evaluated"` with no [[EvaluationError]]its [[ExecPromise]] set to a Promise resolved to *undefined*.

+

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"`)via Promise rejections, which form a chain through the whole dependency graph due to ExecuteModuleWhenImportsReady. Hence both _A_ and _B_ become `"evaluated"` and the exception is recorded in both _A_ and _B_'s [[EvaluationError]][[EvaluationResult]] fields, while _C_ is left as `"evaluated"` with no [[EvaluationError]]its [[EvaluationResult]] set to *undefined*.