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

Editorial: spec refactor of PromiseReaction records/PromiseReactionJob #584

Merged
merged 1 commit into from
Jun 2, 2016

Conversation

ljharb
Copy link
Member

@ljharb ljharb commented May 26, 2016

The current spec creates fictional “equivalent functions” called “Identity” and “Thrower”, and passes these around with PromiseReaction records.

Instead, this change adds a string “Type” field to PromiseReaction records, and sets the “Handler” field to undefined when none is provided, so that the behavior can be determined solely inside PromiseReactionJob.

This change should be non-observable, hopefully adds a bit more clarity, and helps future Promise proposals have smaller diffs against the spec.

The current spec creates fictional “equivalent functions” called “Identity” and “Thrower”, and passes these around with `PromiseReaction` records.

Instead, this change adds a string “Type” field to `PromiseReaction` records, and sets the “Handler” field to `undefined` when none is provided, so that the behavior can be determined solely inside `PromiseReactionJob`.

This change should be non-observable, and helps future Promise proposals.
@littledan
Copy link
Member

I don't think the result here is much cleaner. Can you elaborate on how this helps Promise.prototype.finally? It seems mostly motivated by that. Don't we want all of the "finally" reactions to run after all of the "resolve" or "reject" reactions anyway? Seems like this would make them intermixed, if you put them all on one List.

I'd like to think a little bit about refactorings in general before committing them, as some implementations (including V8) are working to make some of our code parallel to the spec text. Churn, if it doesn't "pay for itself" by making things cleaner or more possible to take a future change, increases our maintenance burden, either by getting us out of sync from the spec or letting us change our code to match the new spec.

There is still a bit of duplication in the Promise spec, and if there is a refactoring and we do want to pay for this churn, it might be nice to clean that up around the same time.

cc @gsathya

@ljharb
Copy link
Member Author

ljharb commented May 27, 2016

@littledan no, finally reactions are run in the order they were added, just like then and catch (not last). In other words, they are definitely intermixed.

The spec text for Promise#finally contains this change, as well as some extra lines for the "Finally" case - which has to be different since the passed-through result may need to be preserved, depending on what the handler does (whereas with resolve/reject, the original result is irrelevant, only the handler result matters).

@littledan
Copy link
Member

Oh, I see, there are multiple lists still and the reaction type recorded.

Seems like the information is held slightly redundantly. One list can have the fulfills or finallys, and the other list can have rejects or finallys.

What if you accomplished this instead by having a bit indicating whether the reaction is a finally reaction? Or, for an even more localized change, just wrap the finally callback in something that returns/rethrows its input?

@ljharb
Copy link
Member Author

ljharb commented May 30, 2016

That bit is the "Type" field on the PromiseReaction record.

The finally spec is a bit more complex than that - it simply can't be represented by a single callback function at the time the "Thrower" and "Identity" are defined.

@ljharb
Copy link
Member Author

ljharb commented May 30, 2016

Put another way, a "finally" reaction is both a fulfill and a reject reaction, so it pushes a function onto both stacks.

@littledan
Copy link
Member

I guess by 'bit' I meant one that doesn't redundantly include whether it's a fulfill or reject, just whether it's finally or not (since this information is redundant). Or, if you do include that redundant information, then there should be a lot of assertions that it's all redundant.

I don't really see what pushing on both stacks has to do with whether you could specify it through wrapping the user's callback to avoid adding a representation of this bit.

@ljharb
Copy link
Member Author

ljharb commented May 31, 2016

@littledan I'd love your thoughts on http://ljharb.github.io/proposal-promise-finally/ - this PR grew out of that spec text, and I feel pretty strongly that this PR is the right direction for it to go. If you could help me tweak the proposal text to use the "bit" you have in mind, I'd be happy to tweak this PR in kind.

In general, though, I think it's important that we are free to do nonobservable spec refactors at any time, and that an implementation's choice to make their code look like the spec text not weigh in to how we word things. Spec refactors should be low cost.

1. If _handler_ is *undefined*, then
1. If _type_ is `"Fulfill"`, let _handlerResult_ be NormalCompletion(_argument_).
1. Else if _type_ is `"Reject"`, let _handlerResult_ be Completion {[[Type]]: ~throw~, [[Value]]: _argument_, [[Target]]: ~empty~}.
1. Assert: _handlerResult_ is defined.
Copy link
Member

Choose a reason for hiding this comment

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

This assert seems unhelpful as "defined" is not a real term.

Copy link
Member Author

Choose a reason for hiding this comment

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

I wanted some kind of assertion so that implementations know that no other _type_ value is expected. How else could I word it?

Copy link
Member

Choose a reason for hiding this comment

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

If you want that, I'd change the second bullet to "Otherwise, type is "Reject". Let handlerResult ..."

@domenic
Copy link
Member

domenic commented Jun 1, 2016

I think this is a good change, as it keeps the types more straight instead of having [[Handler]] be sometimes a function and sometimes a string that triggers special behavior. Instead the special behavior is kept in a sideband.

In other words, right now the [[Handler]] slot is reused as a weird kind of union and you do type-testing on it to figure out what it's being used for. I'd rather keep that type information in an explicit field instead of storing both strings and functions there and branching based on whether it's a string or function.

One of the first things an implementation does to optimize promises is presumably make promise reaction records to be much more lightweight than the spec. E.g. using a globally-reused identity/thrower function. I can't imagine this being any more harmful for implementations than the current spec is, which encourages polymorphic behavior for one of the most important fields in the promise spec.

@ljharb ljharb force-pushed the promise_spec_tweak branch from fea52ac to 559a421 Compare June 2, 2016 00:50
@bterlson
Copy link
Member

bterlson commented Jun 2, 2016

I find this refactoring easier to understand as well. I might prefer a version where [[Handler]] is never undefined and we store a ginned-up identity/thrower function in the slot at appropriate times. In this case the finally spec could easily get by with an [[isFinally]] slot as resolution and rejection reactions would be handled uniformly. But as it stands this is an improvement so I'm happy to take it.

Either `"Fulfill"` or `"Reject"`.
</td>
<td>
The [[Type]] is used when [[Handler]] is *undefined*, to allow for behavior specific to the settlement type.
Copy link
Member

Choose a reason for hiding this comment

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

This comma is superfluous.

Copy link
Member Author

Choose a reason for hiding this comment

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

To me it makes the prose read better, but sure, I can remove it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@ljharb ljharb force-pushed the promise_spec_tweak branch from 559a421 to 636aec2 Compare June 2, 2016 17:06
@bterlson bterlson merged commit 3c9e1fb into tc39:master Jun 2, 2016
@ljharb ljharb deleted the promise_spec_tweak branch June 2, 2016 17:10
ljharb added a commit to ljharb/ecma262 that referenced this pull request Jun 2, 2016
This unobservable spec refactor allows the `Promise#finally` proposal to make a much smaller change, so it can reuse some of the logic in PerformPromiseThen.

(following up on tc39#584)
ljharb added a commit to tc39/proposal-promise-finally that referenced this pull request Jun 2, 2016
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.

4 participants