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

Better closures, or promise-handling, in the spec #933

Closed
domenic opened this issue Jun 13, 2017 · 6 comments · Fixed by #2439
Closed

Better closures, or promise-handling, in the spec #933

domenic opened this issue Jun 13, 2017 · 6 comments · Fixed by #2439

Comments

@domenic
Copy link
Member

domenic commented Jun 13, 2017

Over in the async iteration spec, we're having to write up more and more annoying "closures" as sections. Examples, from the current spec and from async iteration:

The vast majority of these are basically trying to respond to promises, running some spec steps in reaction to their fulfillment or rejection. (Those that are not: promise-rejection-functions, promise-resolve-functions, and proxy-revocation-functions.)

I would love to explore one or both of the following paths:

  1. A way of writing these closures inline, in the algorithms that need to create them, as you would do in ECMAScript.
    • Probably we would want to preserve the current spec's explicit listing of "closed over" values, unlike ECMAScript code.
  2. Algorithmic conventions for operating on promises that allow us to generally react to promises. Web specs are already using some; of those I think the most important are, in order from most- to less-important:
    • Upon fulfillment / upon rejection
    • Transforming
    • A promise resolved with x / a promise rejected with x
Example of (1)

CreateResolvingFunctions(promise)

When CreateResolvingFunctions is performed with argument promise, the following steps are taken:

  1. Let alreadyResolved be a false.
  2. Let resolve be the built-in function object defined by the following steps, [closing over] promise and alreadyResolved, and taking the argument resolution:
    1. If alreadyResolved is true, return undefined.
    2. Set alreadyResolved to true.
    3. If SameValue(resolution, promise) is true, then...
    4. ...
  3. Let reject be the built-in function object defined by the following steps, [closing over] promise and alreadyResolved, and taking the argument reason:
    1. If alreadyResolved is true, return undefined.
    2. Set alreadyResolved to true.
    3. Return RejectPromise(promise, reason).
  4. Return a new Record { [[Resolve]]: resolve, [[Reject]]: reject }.
Example of (2)

AsyncFunctionAwait ( value )

  1. Let asyncContext be the running execution context.
  2. Let valueAsPromise be [a promise resolved with] value.
  3. Upon fulfillment of valueAsPromise with fulfillmentValue, perform the following steps, [closing over] asyncContext:
    1. Let prevContext be the running execution context.
    2. Suspend prevContext.
    3. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
    4. Resume the suspended evaluation of asyncContext using NormalCompletion(fulfillmentValue) as the result of the operation that suspended it. Let result be the value returned by the resumed computation.
    5. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
    6. Return Completion(result).
  4. Upon rejection of valueAsPromise with rejectionReason, perform the following steps, [closing over] asyncContext:
    1. Let prevContext be the running execution context.
    2. Suspend prevContext.
    3. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
    4. Resume the suspended evaluation of asyncContext using Completion{[[Type]]: throw, [[Value]]: rejectionReason, [[Target]]: empty} as the result of the operation that suspended it. Let result be the value returned by the resumed computation.
    5. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
    6. Return Completion(result).
  5. 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.
  6. Set the code evaluation state of asyncContext such that when evaluation is resumed with a Completion resumptionValue the following steps will be performed:
    1. Return resumptionValue.
  7. Return.

I am willing to spend significant time refactoring the current spec if I can get the go-ahead, for either (1) or (2) or both.

@bterlson
Copy link
Member

I like upon-fulfillment and upon-rejection a lot (with "closures"). How frequently are the others coming up in Async Iteration work?

What is the concern with omitting the "closing over" clause? The default assumption of "indented steps see outer named aliases" seems fine.

@domenic
Copy link
Member Author

domenic commented Jun 13, 2017

How frequently are the others coming up in Async Iteration work?

"a promise resolved with" is very frequent. The others less so... I was thinking "transforming" would be used by Promise.prototype.finally or Promise.all, but I see those call the public API methods, so that doesn't quite work. And I guess it shouldn't; we should use proper closure machinery, possibly made simpler by (1), for closures that are actually exposed to author code.

What is the concern with omitting the "closing over" clause? The default assumption of "indented steps see outer named aliases" seems fine.

I can't be sure, but I vaguely imagine it's to make you aware of the "cost" of the closure, and to make it easier to translate into languages (like C++) which don't have auto-capturing closures? I'd be OK omitting it.

@jmdyck
Copy link
Collaborator

jmdyck commented Jun 14, 2017

The spec uses the term "internal closure" for (roughly) an anonymous abstract op with access to 'outer' metavariables. Path (1) is proposing (roughly) an anonymous built-in function object whose call-semantics are given by steps with access to 'outer' metavariables. The spec should make it clear (via naming and description) that the two things are similar but distinct.

@domenic
Copy link
Member Author

domenic commented Jun 14, 2017

Goodness, I had no idea that the spec had a notion of "internal closure" already. I see it is only used inside the notation of RegExp pattern semantics; strange.

@jmdyck
Copy link
Collaborator

jmdyck commented Jun 14, 2017

Yeah, there's a lot of strange stuff in RegExp semantics!

@littledan
Copy link
Member

Yeah, I was a bit confused at first by how the data passing in the RegExp spec works, though I imagine it's done that way because things would be very wordy if it were all explicit.

Anyway, the RegExp usage of closures is pretty different from the Promise usage; it seems like they should be addressed separately.

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 a pull request may close this issue.

4 participants