From 17cafa596db5091b0dbb357319fe69d57a592887 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Mon, 3 Sep 2018 01:00:32 +0200 Subject: [PATCH] wip - promise refactoring --- spec.html | 485 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 385 insertions(+), 100 deletions(-) diff --git a/spec.html b/spec.html index 1fe74d4..1a34ca7 100644 --- a/spec.html +++ b/spec.html @@ -66,124 +66,409 @@

AsyncBlockStart ( _promiseCapability_, _asyncBody_, _asyncContext_ ) - -

Evaluate( ) Concrete Method

+ +

AwaitWithReturn( _promise_ )

+ + 1. Let _resolveCapability_ be ! NewPromiseCapability(%Promise%). + 1. Perform ! Call(_resolveCapability_.[[Resolve]], *undefined*, «_promise_»). + 1. Let _resolvedPromise_ be _resolveCapability_.[[Promise]]. + 1. Let _stepsFulfilled_ be the following steps of the algorithm that invoked Await, with argument value. + 1. Let _onFulfilled_ be _CreateBuiltinFunction_(_stepsFulfilled_). + 1. Let _resultCapability_ be ! NewPromiseCapability(%Promise%). + 1. Perform ! PerformPromiseThen(_resolvedPromise_, _onFulfilled_, *undefined*, _resultCapability_). + 1. Return _resultCapability_. + +
-

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

-

By the time the promise returned by Evaluate settles, Evaluate transitions this module's [[Status]] from `"instantiated"` to `"evaluated"`.

+ +

AwaitAll( _promises_ )

+ + // TODO / HELP: Spec-based Promise.all for a List of Promises + +
-

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

+ +

PerformBuiltinPromiseThen( _builtinFunction_, _onFulfilled_, _onRejected_ )

+ + // TODO / HELP: spec-based Promise.then for a builtinFunction, + // returning synchronously, but adding the execution to the execution stack + +
-

If execution results in a rejected promise, the promise's rejection reason is recorded in the [[EvaluationError]] field. Future invocations of Evaluate will then return a new promise rejected with that same rejection reason.

+ +

Abstract Module Records

+

A Module Record encapsulates structural information about the imports and exports of a single module. This information is used to link the imports and exports of sets of connected modules. A Module Record includes four fields that are only used when evaluating a module.

+

For specification purposes Module Record values are values of the Record specification type and can be thought of as existing in a simple object-oriented hierarchy where Module Record is an abstract class with concrete subclasses. This specification only defines a single Module Record concrete subclass named Source Text Module Record. Other specifications and implementations may define additional Module Record subclasses corresponding to alternative module definition facilities that they defined.

+

Module Record defines the fields listed in . All Module Definition subclasses include at least those fields. Module Record also defines the abstract method list in . All Module definition subclasses must provide concrete implementations of these abstract methods.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 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 instantiated. +
+ [[Namespace]] + + Object | *undefined* + + The Module Namespace Object () if one has been created for this module. Otherwise *undefined*. +
+ [[EvalPromise]] + + Promise | *undefined* + + The evaluation promise for this Abstract Module Record, including any dependency evaluations. +
+ [[HostDefined]] + + Any, default value is *undefined*. + + Field reserved for use by host environments that need to associate additional information with a module. +
+
+
-

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

+ +

Source Text Module Records

- - 1. Let _module_ be this Source Text Module Record. - 1. Assert: _module_.[[Status]] is `"instantiated"` or `"evaluated"`. - 1. Let _promiseCapability_ be ! NewPromiseCapability(%Promise%). - 1. Let _stack_ be a new empty List. - 1. Let _result_ be Await(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. Set _m_.[[EvaluationError]] to _result_. - 1. Assert: _module_.[[Status]] is `"evaluated"` and _module_.[[EvaluationError]] is _result_. - 1. Return _result_. - 1. Perform ! Call(_promiseCapability_.[[Reject]], *undefined*, «_result_.[[Value]]»). - 1. Return _promiseCapability_.[[Promise]]. - 1. Assert: _module_.[[Status]] is `"evaluated"` and _module_.[[EvaluationError]] is *undefined*. - 1. Assert: _stack_ is empty. - 1. Return *undefined*. - 1. Perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, «*undefined*»). - 1. Return _promiseCapability_.[[Promise]]. - +

A Source Text Module Record is used to represent information about a module that was defined from ECMAScript source text () that was parsed using the goal symbol |Module|. Its fields contain digested information about the names that are imported by the module and its concrete methods use this digest to link, instantiate, and evaluate the module.

- -

InnerModuleEvaluation( _module_, _stack_, _index_ )

+

A Source Text Module Record can exist in a module graph with other subclasses of the abstract Module Record type. However, non-source text Module Records must not participate in dependency cycles with Source Text Module Records.

-

The InnerModuleEvaluation abstract operation is used by Evaluate to perform the actual evaluation process for the Source Text 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 InnerModuleInstantiation.

+

In addition to the fields, defined in , Source Text Module Records have the additional fields listed in . Each of these fields is initially set in ParseModule.

-

This abstract operation performs the following steps:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Field Name + + Value Type + + Meaning +
+ [[ECMAScriptCode]] + + a Parse Node + + The result of parsing the source text of this module using |Module| as the goal symbol. +
+ [[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. +
+ [[ImportEntries]] + + List of ImportEntry Records + + A List of ImportEntry records derived from the code of this module. +
+ [[LocalExportEntries]] + + List of ExportEntry Records + + A List of ExportEntry records derived from the code of this module that correspond to declarations that occur within the module. +
+ [[IndirectExportEntries]] + + List of ExportEntry Records + + A List of ExportEntry records derived from the code of this module that correspond to reexported imports that occur within the module. +
+ [[StarExportEntries]] + + List of ExportEntry Records + + A List of ExportEntry records derived from the code of this module that correspond to export * declarations that occur within the module. +
+ [[Status]] + + String + + Initially `"uninstantiated"`. Transitions to `"instantiating"`, `"instantiated"`, `"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 Instantiate and Evaluate only. + If [[Status]] is `"instantiating"` or `"evaluating"`, this non-negative number records the point at which the module was first visited during the ongoing depth-first traversal of the dependency graph. +
+ [[DFSAncestorIndex]] + + Integer | *undefined* + + Auxiliary field used during Instantiate and Evaluate only. If [[Status]] is `"instantiating"` or `"evaluating"`, this is either the module's own [[DFSIndex]] or that of an "earlier" module in the same strongly connected component. +
+
+ +

ParseModule ( _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. Let _promiseCapability_ be ! NewPromiseCapability(%Promise%). - 1. If _module_ is not a Source Text Module Record, then - 1. Perform _module_.Evaluate(). - 1. Return _index_. - 1. Let _evaluateResult_ be Await(! _module_.Evaluate()) - 1. If _evaluateResult_ is an abrupt completion, perform ! Call(_promiseCapability_.[[Reject]], *undefined*, «_evaluateResult_.[[Value]]»). - 1. Otherwise, perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, «_index_»). - 1. Return _promiseCapability_.[[Promise]]. - 1. If _module_.[[Status]] is `"evaluated"`, then - 1. If _module_.[[EvaluationError]] is *undefined*, return _index_. - 1. Otherwise return _module_.[[EvaluationError]]. - 1. If _module_.[[EvaluationError]] is an abrupt completion, perform ! Call(_promiseCapability_.[[Reject]], *undefined*, «_module_.[[EvaluationError]].[[Value]]»). - 1. Otherwise, perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, «_index_»). - 1. Return _promiseCapability_.[[Promise]] - 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. Set _index_ to _index_ + 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. 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. Let _childResult_ be Await(! InnerModuleEvaluation(_requiredModule_, _stack_, _index_). - 1. IfAbruptRejectPromise(_childResult_, _promiseCapability_). - 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 `"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 _executionResult_ be Await(! ModuleExecution(_module_)). - 1. IfAbruptRejectPromise(_executionResult_, _promiseCapability_). - 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 `"evaluated"`. - 1. If _requiredModule_ and _module_ are the same Module Record, set _done_ to *true*. - 1. Return _index_. - 1. Perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, «_index_»). - 1. Return _promiseCapability_.[[Promise]] + 1. Assert: _sourceText_ is an ECMAScript source text (see clause ). + 1. 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. + 1. If _body_ is a List of errors, return _body_. + 1. Let _requestedModules_ be the ModuleRequests of _body_. + 1. Let _importEntries_ be ImportEntries of _body_. + 1. Let _importedBoundNames_ be ImportedLocalNames(_importEntries_). + 1. Let _indirectExportEntries_ be a new empty List. + 1. Let _localExportEntries_ be a new empty List. + 1. Let _starExportEntries_ be a new empty List. + 1. Let _exportEntries_ be ExportEntries of _body_. + 1. 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_. + 1. Else, + 1. Let _ie_ be the element of _importEntries_ whose [[LocalName]] is the same as _ee_.[[LocalName]]. + 1. If _ie_.[[ImportName]] is `"*"`, then + 1. Assert: This is a re-export of an imported module namespace object. + 1. Append _ee_ to _localExportEntries_. + 1. 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_. + 1. Else if _ee_.[[ImportName]] is `"*"`, then + 1. Append _ee_ to _starExportEntries_. + 1. Else, + 1. Append _ee_ to _indirectExportEntries_. + 1. Return Source Text Module Record { [[Realm]]: _realm_, [[Environment]]: *undefined*, [[Namespace]]: *undefined*, [[EvalPromise]]: *undefined*, [[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.

+
- -

ModuleExecution( _module_ )

+ +

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 [[EvalPromise]] Promise field is populated to a promise resolving on completion of the module execution, including its dependency executions, or the associated execution error..

-

The ModuleExecution abstract operation is used by InnerModuleEvaluation to initialize the execution context of the module and evaluate the module's code within it.

+

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

-

This abstract operation performs the following steps:

+

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

- 1. Let _moduleCxt_ be a new ECMAScript code execution context. - 1. Set the Function of _moduleCxt_ to *null*. - 1. Assert: _module_.[[Realm]] is not *undefined*. - 1. Set the Realm of _moduleCxt_ to _module_.[[Realm]]. - 1. Set the ScriptOrModule of _moduleCxt_ to _module_. - 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. Let _promiseCapability_ be ! NewPromiseCapability(%Promise%). - 1. Suspend the currently running execution context. - 1. Perform ! AsyncBlockStart(_promiseCapability_, _module_.[[ECMAScriptCode]], _moduleCxt_). - 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. Return _promiseCapability_.[[Promise]]. + 1. Let _module_ be this Source Text Module Record. + 1. Assert: _module_.[[Status]] is `"instantiated"`, `"evaluating"` or `"evaluated"`. + 1. Let _stack_ be a new empty List. + 1. Perform ! InnerModuleEvaluation(_module_, _stack_, 0). + 1. Assert: _stack_ is empty. + 1. Let _result_ be AwaitWithReturn(_module_.[[EvalPromise]]). + 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: _module_.[[Status]] is `"evaluated"` and _module_.[[EvaluationError]] is *undefined*. + 1. Assert: _stack_ is empty. + 1. Return *undefined*. + + +

InnerModuleEvaluation( _module_, _stack_, _index_ )

+ +

The InnerModuleEvaluation abstract operation is used by Evaluate to perform the actual evaluation process for the Source Text 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 InnerModuleInstantiation.

+ +

This abstract operation performs the following steps:

+ + + 1. If _module_ is not a Source Text Module Record, then + 1. Set _module_.[[EvalPromise]] 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_.[[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_.[[EvalPromise]] to _evalCapability.[[Promise]]_. + 1. Set _index_ to _index_ + 1. + 1. Append _module_ to _stack_. + 1. Let _dependencyExecPromises_ 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 `"evaluating"`, then + 1. Assert: _requiredModule_ is a Source Text Module Record. + 1. Set _module_.[[DFSAncestorIndex]] to min(_module_.[[DFSAncestorIndex]], _requiredModule_.[[DFSAncestorIndex]]). + 1. Let _stackErrorCapability_ be ! NewPromiseCapability(%Promise%). + 1. Perform ! PerformPromiseThen(_requiredModule_.[[ExecPromise]], *undefined*, _stackErrorCapability_.[[Reject]]). + 1. Add _stackErrorCapability_ to the list _dependencyExecPromises_. + 1. Perform ? ModuleExecution(_module_). + 1. Let _executionSteps_ be the algorithm steps defined in ModuleExecution. + 1. Let _execution_ be CreateBuiltinFunction(_executionSteps_, « Module, DependencyExecPromises, Resolve, Reject »). + 1. Set _execution_.[[Module]] to _module_. + 1. Set _execution_.[[DependencyExecPromises]] to _dependencyExecPromises_. + 1. Perform ! PerformBuiltinPromiseThen(_execution_, _evalCapability_.[[Resolve]], _evalCapability_.[[Reject]]). + 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 `"evaluated"`. + 1. If _requiredModule_ and _module_ are the same Module Record, set _done_ to *true*. + 1. Return _index_. + +
+ + +

ModuleExecution( _module_, _dependencyExecPromises_, _resolve_, _reject_ )

+ +

The ModuleExecution abstract operation is used by InnerModuleEvaluation to initialize the execution context of the module and evaluate the module's code within it.

+ +

This abstract operation performs the following steps:

+ + + 1. Perform ? AwaitAll(_dependencyExecPromises_). + 1. Let _moduleCxt_ be a new ECMAScript code execution context. + 1. Set the Function of _moduleCxt_ to *null*. + 1. Assert: _module_.[[Realm]] is not *undefined*. + 1. Set the Realm of _moduleCxt_ to _module_.[[Realm]]. + 1. Set the ScriptOrModule of _moduleCxt_ to _module_. + 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. Perform ! AsyncBlockStart(_promiseCapability_, _module_.[[ECMAScriptCode]], _moduleCxt_). + 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. AwaitWithReturn(_promiseCapability_.[[Promise]]) + +
+ + +

DependencyCompletion (_module_)

+ +

The DependencyCompletion abstract operation is used by ModuleExecution to determine when the dependencies

+