Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various editorial improvements #1

Merged

Conversation

littledan
Copy link

See individual commit messages for details.

This reverts commit e4ee72a.

The spec-level Promise.all here was well-written! Let's keep it, but
consider follow-on editorial tweaks to match ES spec style.
- Evaluate() method returns a Promise which settles when the module
  is ready.
- Move the [[ExecPromise]] internal slot to Source Text Module Record
  (eventual intended home: Circular Module Record).
- Avoid using constructs like AwaitWithReturn, BuiltinPromiseThen
  and even Await, and instead deal with Promise reactions directly
  for clarity.
- Specialize the spec-level Await to the particular case of continuing
  on to execute the module.
- Various small editorial cleanups/fixes.
littledan added a commit to littledan/html that referenced this pull request Feb 10, 2019
The purpose of this patch is to nail down how HTML would
adjust to the needs of top-level await. The relevant change is,
the module.Evaluate() method will return a Promise that resolves
when the module is ready, and this will be passed to
FinishDynamicImport to wait on before resolving the Promise it returns.
It doesn't seem like anyone else needs to wait for a module script
to "finish" evaluating besides dynamic import.

This PR is just a sketch, not to be landed. Top-level await should
reach stage 3 before this change lands.

The PR where Evaluate returns a Promise is
guybedford/proposal-top-level-await#1
littledan added a commit to littledan/proposal-dynamic-import that referenced this pull request Feb 10, 2019
The purpose of this patch is to nail down how dynamic import would
adjust to the needs of top-level await. The relevant change is,
the module.Evaluate() method will return a Promise that resolves
when the module is ready, and this will be passed to
FinishDynamicImport to wait on before resolving the Promise it returns.

This PR is just a sketch, not to be landed. For one, the "Upon fulfillment"
language will need to be changed to be more ECMASpeak-like. Top-level
await should also reach stage 3 before this change lands.

The PR where Evaluate returns a Promise is
guybedford/proposal-top-level-await#1
@littledan
Copy link
Author

See matching HTML and dynamic import PRs.

Copy link
Owner

@guybedford guybedford left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, thanks @littledan so much for picking this up, would love to see this move forward.

Please feel free to close out my PR and continue this as your own PR directly against the top-level-await repo. I will gladly collaborate further as well.

1. Perform ! ModuleExecution(_module_, _capability_).
1. Return.
1. Let _index_ be 0.
1. Let _fullfilledCount_ be 0.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could abstract this machinery as a spec-based Promise.all since it is so common?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe? I would rather wait for a second use case to come along so I can figure out what is needed in general.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that is understandable. If I recall this pattern already exists in some other async spec code, but I can't recall where exactly.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within the JS spec, or in higher-level specifications? I have only seen it in the W3C Promise guide.

I doubt the W3C Promise guide would reference a Stage 2 draft; maybe we could make this editorial change at some later point (e.g., when integrating into the main spec) when it would make sense for that reference to be added.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I think the only other similar pattern is in the implementation of Promise.all itself :)

spec.html Outdated
1. Perform ? _m_.Instantiate().
1. Assert: All dependencies of _m_ have been transitively resolved and _m_ is ready for evaluation.
1. <del>Return</del><ins>Let _promise_ be</ins> ? _m_.Evaluate().
1. <ins>Upon rejection of _promise_ with reason _r_,</ins> <!--TODO(littledan): Upgrade to ECMASpeak-->
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to get this updated for merge.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Owner

@guybedford guybedford left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be glad to merge into the PR if you're ready to go here?


<p>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"`.</p>

<p>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() <del>, which records the error in all modules that are currently on its _stack_ (i.e., the modules that are still `"evaluating"`)</del><ins>via Promise rejections, which form a chain through the whole dependency graph due to ExecuteModuleWhenImportsReady</ins>. Hence both _A_ and _B_ become `"evaluated"` and the exception is recorded in both _A_ and _B_'s <del>[[EvaluationError]]</del><ins>[[ExecPromise]]</ins> fields, while _C_ is left as `"evaluated"` with <del>no [[EvaluationError]]</del><ins>its [[ExecPromise]] set to a Promise resolved to *undefined*.</p>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we are doing this one as well though.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

??


<p>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.</p>

<p>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<ins>, returning a Promise resolving to *undefined*</ins>. The resulting exception will be recorded in both _A_ and _B_'s <del>[[EvaluationError]]</del><ins>[[ExecPromise]]</ins> fields <ins>as a rejected Promise</ins>, and their [[Status]] will become `"evaluated"`. _C_ will also become `"evaluated"` but, in contrast to _A_ and _B_, will <del>remain without an [[EvaluationError]]</del><ins>have its [[ExecPromise]] internal slot set to a Promise resolved to *undefined*</ins>, as it successfully completed evaluation. Storing the exception <ins>in [[ExecPromise]] as a Promse rejection</ins> 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.)</p>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't think we could just include this line to avoid noise here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, what are you suggesting?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just that there is a lot of text here from the spec, and it wasn't easy to see the diff. But I suppose that will be clearer in the rendered output.

1. Perform ! ModuleExecution(_module_, _capability_).
1. Return.
1. Let _index_ be 0.
1. Let _fullfilledCount_ be 0.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that is understandable. If I recall this pattern already exists in some other async spec code, but I can't recall where exactly.

@littledan
Copy link
Author

Yeah feel free to merge when you think it's ready. I'd rather we keep using the same PR against top-level await, as your PR represents the change to Variant B, and this PR just edits some details. Thanks for the review!

@guybedford guybedford merged commit bca2895 into guybedford:promise-refactoring Feb 17, 2019
@guybedford
Copy link
Owner

Amazing, thanks. Updated. What do you think would be suitable next steps to keep this moving forward?

@littledan
Copy link
Author

I will talk with other TC39 folks about what we can do to get this presented at the March or May meeting (hopefully without me actually giving the presentation).

Ms2ger pushed a commit to littledan/html that referenced this pull request May 17, 2019
The purpose of this patch is to nail down how HTML would
adjust to the needs of top-level await. The relevant change is,
the module.Evaluate() method will return a Promise that resolves
when the module is ready.

Top-level await should reach stage 3 before this change lands.

The PR where Evaluate returns a Promise is
guybedford/proposal-top-level-await#1
Ms2ger pushed a commit to littledan/html that referenced this pull request Jun 12, 2019
The purpose of this patch is to nail down how HTML would
adjust to the needs of top-level await. The relevant change is,
the module.Evaluate() method will return a Promise that resolves
when the module is ready.

Top-level await should reach stage 3 before this change lands.

The PR where Evaluate returns a Promise is
guybedford/proposal-top-level-await#1
Ms2ger pushed a commit to littledan/html that referenced this pull request Jun 24, 2019
The purpose of this patch is to nail down how HTML would
adjust to the needs of top-level await. The relevant change is,
the module.Evaluate() method will return a Promise that resolves
when the module is ready.

Top-level await should reach stage 3 before this change lands.

The PR where Evaluate returns a Promise is
guybedford/proposal-top-level-await#1
Ms2ger pushed a commit to littledan/proposal-dynamic-import that referenced this pull request Jun 24, 2019
The purpose of this patch is to nail down how dynamic import would
adjust to the needs of top-level await. The relevant change is,
the module.Evaluate() method will return a Promise that resolves
when the module is ready, and this will be passed to
FinishDynamicImport to wait on before resolving the Promise it returns.

This PR is just a sketch, not to be landed. For one, the "Upon fulfillment"
language will need to be changed to be more ECMASpeak-like. Top-level
await should also reach stage 3 before this change lands.

The PR where Evaluate returns a Promise is
guybedford/proposal-top-level-await#1
Ms2ger pushed a commit to littledan/html that referenced this pull request Sep 20, 2019
The purpose of this patch is to nail down how HTML would
adjust to the needs of top-level await. The relevant change is,
the module.Evaluate() method will return a Promise that resolves
when the module is ready.

Top-level await should reach stage 3 before this change lands.

The PR where Evaluate returns a Promise is
guybedford/proposal-top-level-await#1
littledan added a commit to littledan/html that referenced this pull request Jun 10, 2020
The purpose of this patch is to nail down how HTML would
adjust to the needs of top-level await. The relevant change is,
the module.Evaluate() method will return a Promise that resolves
when the module is ready, and this will be passed to
FinishDynamicImport to wait on before resolving the Promise it returns.
It doesn't seem like anyone else needs to wait for a module script
to "finish" evaluating besides dynamic import.

This PR is just a sketch, not to be landed. Top-level await should
reach stage 3 before this change lands.

The PR where Evaluate returns a Promise is
guybedford/proposal-top-level-await#1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants