diff --git a/README.md b/README.md index d515cb9..de1912f 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 has already completed (e.g., if no top-level `await` is reached), there will be no entry in the `Promise.all` for that module. If a module has no dependencies which are asynchronous in this way, 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 2096325..6e66db2 100644 --- a/spec.html +++ b/spec.html @@ -17,34 +17,8 @@

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.

- -

AsyncFunctionStart ( _promiseCapability_, _asyncFunctionBody_ )

- - 1. Let _runningContext_ be the running execution context. - 1. Let _asyncContext_ be a copy of _runningContext_. - 1. Perform ! AsyncBlockStart(_promiseCapability_, _asyncFunctionBody_, _asyncContext_). - 1. 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_. - 1. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done. - 1. 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. - 1. If _result_.[[Type]] is ~normal~, then - 1. Perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, «*undefined*»). - 1. Else if _result_.[[Type]] is ~return~, then - 1. Perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, «_result_.[[Value]]»). - 1. Else, - 1. Assert: _result_.[[Type]] is ~throw~. - 1. Perform ! Call(_promiseCapability_.[[Reject]], *undefined*, «_result_.[[Value]]»). - 1. Return. - 1. Push _asyncContext_ onto the execution context stack; _asyncContext_ is now the running execution context. - 1. Resume the suspended evaluation of _asyncContext_. Let _result_ be the value returned by the resumed computation. - 1. Assert: When we return here, _asyncContext_ has already been removed from the execution context stack and _runningContext_ is the currently running execution context. - 1. 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. - 1. Return. - -
- - -

AsyncBlockStart ( _promiseCapability_, _asyncBody_, _asyncContext_ )

+ +

AsyncModuleStart ( _promiseCapability_, _asyncBody_, _asyncContext_, _moduleRecord_ )

1. Let _runningContext_ be the running execution context. 1. Set the code evaluation state of _asyncContext_ such that when evaluation is resumed for that execution context the following steps will be performed: @@ -53,11 +27,13 @@

AsyncBlockStart ( _promiseCapability_, _asyncBody_, _asyncContext_ )Source Text Module Records

- [[ExecPromise]] + [[EvaluationResult]] - Promise | *undefined* + ~empty~ | Promise | *undefined* | an abrupt completion - The evaluation promise for this Abstract Module Record, including any dependency evaluations. + The evaluation result for this Abstract Module Record, including any dependency evaluations. If the module has not started to execute, it will be ~empty~. If a top-level await was reached, the result will be a Promise. @@ -296,7 +272,7 @@

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. Return Source Text Module Record { [[Realm]]: _realm_, [[Environment]]: *undefined*, [[Namespace]]: *undefined*, [[EvaluationResult]]: ~empty~, [[Status]]: `"uninstantiated"`, [[EvaluationError]]: *undefined*, [[HostDefined]]: _hostDefined_, [[ECMAScriptCode]]: _body_, [[RequestedModules]]: _requestedModules_, [[ImportEntries]]: _importEntries_, [[LocalExportEntries]]: _localExportEntries_, [[IndirectExportEntries]]: _indirectExportEntries_, [[StarExportEntries]]: _starExportEntries_, [[DFSIndex]]: *undefined*, [[DFSAncestorIndex]]: *undefined* }.

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.

@@ -307,9 +283,9 @@

ParseModule ( _sourceText_, _realm_, _hostDefined_ )

Evaluate( ) Concrete Method

The Evaluate concrete method of a Source Text 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"`, at which point the [[EvaluationResult]] field is populated to a representation of the result of execution..

-

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 execution results in a synchronous exception, that exception is recorded in the [[EvaluationError]][[EvaluationResult]] field and rethrown by future invocations of Evaluate.

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

@@ -317,21 +293,18 @@

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. Set _m_.[[Status]] to `"evaluated"`. + 1. Assert: _m_.[[EvaluationResult]] is ~empty~. + 1. Set _m_.[[EvaluationErrorResult]] to _result_. + 1. Assert: _module_.[[Status]] is `"evaluated"` and _module_.[[EvaluationErrorResult]] is _result_. + 1. Return _result_. + 1. Assert: _module_.[[Status]] is `"evaluated"` and _module_.[[EvaluationErrorResult]] is either a Promise or*undefined*. + 1. Assert: _stack_ is empty. + 1. Return *undefined*. _module_.[[EvaluationResult]]. @@ -343,19 +316,16 @@

InnerModuleEvaluation( _module_, _stack_, _index_ )

1. If _module_ is not a Source Text Module Record, then - 1. Set _module_.[[ExecPromise]] to _module_.Evaluate(). + 1. Set _module_.[[EvaluationResult]] to _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_.[[EvaluationErrorResult]]. 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. @@ -366,12 +336,16 @@

InnerModuleEvaluation( _module_, _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_.[[EvaluationResult]] a Promise, then + 1. Add _requiredModule_.[[EvaluationResult]] to the list _dependencyExecPromises_. 1. If _requiredModule_.[[Status]] is `"evaluating"`, then 1. Assert: _requiredModule_ is a Source Text Module Record. 1. Set _module_.[[DFSAncestorIndex]] to min(_module_.[[DFSAncestorIndex]], _requiredModule_.[[DFSAncestorIndex]]). 1. Perform ? ModuleExecution(_module_). + 1. Let _evalCapability_ be ! NewPromiseCapability(%Promise%). 1. Perform ! ExecuteModuleWhenImportsReady(_module_, _dependencyExecPromises_, _evalCapability_). + 1. If _module_.[[EvaluationResult]] is ~empty~, + 1. Set _module_.[[EvaluationResult]] to _evalCapability_.[[Promise]]. 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 @@ -381,7 +355,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_.[[EvaluationResult]] is a Promise, + 1. Set _requiredModule_.[[EvaluationResult]] to _module_.[[EvaluationResult]]. 1. Return _index_.
@@ -435,7 +410,7 @@

ModuleExecution( _module_, _capability_)

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. Perform ! AsyncModuleStart(_capability_, _module_.[[ECMAScriptCode]], _moduleCxt_, _module_). 1. Return.