-
Notifications
You must be signed in to change notification settings - Fork 46
Conversation
17cafa5
to
4150946
Compare
The only strange edge case I can think of with this approach is if you have a cycle like the following: A -> B -> A where each is awaiting, then if I run a top-level evaluation of A, and then run a top-level evaluation of B before A has resolved, then the B [[ExecPromise]] is effectively just a promise for the completion of B only, and as a result the second top-level evaluation job will return complete even if A is still executing. This seemed to me a valid compromise for circular references, but the alternative is to make ExecPromise singular on that execution, and then separately construct a top-level evaluation promise. The issue with this approach is that we no longer store errors directly on the module records of the modules that should throw, but re-propogate up from dependencies each time when handling error caching, but this could be deemed acceptable. Reworking as discussed above can be straightforward though. Alternative ideas welcome too. |
Just wanted to say thanks very much for this work, and it didn't go unnoticed. We're trying to marshall resources within the champion group to do proper review; both Myles and myself have gotten unfortunately busy with other projects. In the meantime, in lieu of a full review, I'll say that the "Algorithmically the following approach is taken:" points all seem pretty sound. For "how to complete the promise handling here", do you mean pointers on how to fill in the TODOs? The Promise.all is pretty easy and I can work on that, but for PerformBuiltinPromiseThen I wonder if you could just replace its usage with
This seems pretty unfortunate. Perhaps we should work through it in more detail and discuss the alternatives in a dedicated issue? E.g. it'd be good to have concrete module bodies for A and B to evaluate what intuitively "should" happen. |
w3ctag/promises-guide#55 has a draft of what a proper spec-level Promise.all might look like. Would need to be adapted to ECMAspeak a bit ("steps" is too wishy-washy, I guess). |
I'm glad to help ensure this is as polished as possible as it reaches these stages.
The TODOs as well as the usages of promises, ensuring the execution stack is being handled correctly.
Would that work if
Actually, since we have AwaitWithReturn, and these promises have all been created already, I just did the
Sure, this is a consequence of combining the stored execution promise with the promise of the dependencies. The alternative would be to have the execution promise only refer to the execution promise of the current module being executed, with each top-level load constructing the full promise for its tree execution (what I originally proposed here - #31 (comment) for these reasons). I can make such change certainly, just let me know. |
Of course the issue with |
Ah, I misread and thought execution was a promise for some reason. OK, this will need some sort of trickiness, nevermind.
I am not really able to understand these two alternatives as stated, sorry. You are deeper into this than I am. That's why I suggested a separate issue for discussion with concrete example module bodies that we could check our understanding against.
What's incorrect about the error handling?
FWIW your version is not technically allowed because ECMAspeak does not allow inlining of steps. I think we should keep your version, but add a note pointing to tc39/ecma262#933. Resolving that is a stage 3 -> stage 4 concern. |
Ok, I was just saying I could try spec the other approach and then we can compare. The canonical example is the one stated, and the differences are not so much execution tradeoffs as just different spec approaches.
If executing A which depends on B and C, then if there were an error in the execution of C, this would only throw after the promise for B's execution resolves. Rather we should throw failure as soon as possible for A.
If I treated the steps as separate concrete methods would that work? |
I don't understand the one stated :). Example JavaScript code would help me understand.
Oh, if there are no runtime differences then I guess I won't worry about this.
This decreases determinism, right? I.e. you'll get a different error depending on whether B or C loads faster. I think we should not throw as soon as possible in this case; we should throw only after everything completes.
Separate concrete functions? Yes, that would work; that's what most other parts of the spec do. But it decreases clarity and bloats the spec. So I think leaving it as-is is better, with a link to the issue about making it legal. |
There are runtime differences. Ok, I've created #35 (1) feels like the most sound to me, but involves a new concept of ownership. (2) is what I was suggesting specifying. and (3) is what is specified. As an extreme edge case, so I'm not too worried about which one we go with though.
What I specified previously was very deterministic - the first module in order to throw is the one that throws. Perhaps we just go back to that then? |
Thanks for opening the other issue! Will discuss there.
Yeah, I like that a lot. |
This reverts commit 796d17c.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At a high level, I wonder if this spec text would be written better in a style which more directly matches the existing specification, either with the Await() macro or direct usage of Promise algorithms. My biggest semantic concern is #43, though this PR doesn't change that.
spec.html
Outdated
|
||
<p>The Evaluate concrete method of a Source Text Module Record implements the corresponding Module Record abstract method.</p> | ||
<p><ins>By the time the promise returned by Evaluate settles, </ins>Evaluate transitions this module's [[Status]] from `"instantiated"` to `"evaluated"`.</p> | ||
<emu-clause id="sec-awaitwithreturn" aoid="AwaitWithReturn"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really understand the purpose of this AwaitWithReturn macro. It looks like one of the usages is attempting to get the completion record, but Await already returns a completion record. I wonder if the other usage (which involves the TBD PerformPromiseThen algorithm) would be described more clearly by specifying an algorithm which ends up calling the resolve or reject methods as appropriate.
spec.html
Outdated
|
||
<emu-alg> | ||
1. <ins>For each Promise _promise_ in _dependencyExecPromises_, do</ins> | ||
1. <ins>AwaitWithReturn(_module_.[[ExecPromise]]).</ins> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this is the behavior we want to have on errors. If one module's Promise rejects, shouldn't we reject this module's Promise immediately, without waiting for the earlier ones to resolve?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the logic in w3ctag/promises-guide#55 handles failures in this way.
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.
Some editorial improvements for this PR proposed at guybedford#1 . Overall, I like the semantics here, after some more consideration in #43, and some offline discussion about alternatives to deadlocking from dynamic import (where, at this point, I'm pretty convinced we shouldn't have built-in deadlock detection). |
- 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.
Various editorial improvements
This PR has now been updated with the latest spec fixes and updates here by @littledan. |
@guybedford @domenic @littledan is this ready to land in its current state? Seems like an improvement from where we are at right now and we can continue to improve in other PRs |
I'd say it's ready to land. We will need follow-on changes, especially for #47 but also maybe for propagating errors better, but this is an improvement that those fixes can be built on. |
I've made a step here towards refactoring the promise handling, based on the current suggestions of storing
[[ExecPromise]]
on the module record itself, as well as using the promises approach mentioned in #18 (comment).My experience with the promises spec is very weak, so there are some gaps where I couldn't find appropriate spec functions for
AwaitAll
andPerformBuiltinPromiseThen
, which I have left as to-dos. Help with this would be great, just let me know if I can explain them further at all.Algorithmically the following approach is taken:
@domenic I would really value your feedback re how to complete the promise handling here. I will try and give it another shot again when I can, but wanted to post this early for initial feedback.