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

Cross-realm invocation of async functions & generators #344

Open
caitp opened this issue Dec 17, 2021 · 8 comments
Open

Cross-realm invocation of async functions & generators #344

caitp opened this issue Dec 17, 2021 · 8 comments
Labels

Comments

@caitp
Copy link

caitp commented Dec 17, 2021

If an async function or generator is wrapped, their invocation will always throw an exception (due to returning a generator object or Promise) -- but this doesn't totally translate to how they're actually used in practice.

Would it make sense for async functions and generators to be wrappable, with wrapper Generator objects and wrapper Promises?

@bathos
Copy link
Contributor

bathos commented Jan 20, 2022

From a JS code observability POV, both async functions and generator functions are two-layers-deep contractual: a function that returns a thenable, a function that returns an iterator/generator.

  • () => function * () { yield 1; }() is "a generator function"
  • () => Promise.resolve(1) is "an async function"
  • () => ({ next() {/*...*/}, /*...*/ }) is "a generator function"
  • () => ({ then: r => r(1) }) is "an async function"

There's no observable brand on syntactic async/generator functions today and it seems like this wrapping would depend on changing that (making non-syntactic realizations second-class as a consequence). For syntactic generator functions, there's also unique prototypes in play whose next/return/throw do not need to be the ancestral built-in methods. (Not sure if that would matter for a wrapper or not.)

I'm not sure exactly what's possible here and may be misunderstanding something, but it's not clear to me how this mapping could be sound in the absence of a generic any-object membrane.

@ljharb
Copy link
Member

ljharb commented Jan 20, 2022

The difference can be observed with Function toString, but without a full JS parser it’s not reliably determinable for every case.

@mhofman
Copy link
Member

mhofman commented Jan 20, 2022

The callable boundary indeed prevents any function returning a non callable object from being wrapped transparently. Async and generator functions are a subset of functions that will indeed always fail when used through the boundary.

In my mind, the current design of the callable boundary is not to be fully transparent, and thus they require the functions being passed across the boundary to be somewhat aware of the boundary primitive constraints.

There are use cases that do not require a full membrane, such as a test runner environment setup. But if code interactions requires full transparency, then yes a membrane is likely required.

@ljharb
Copy link
Member

ljharb commented Jan 20, 2022

It does seem reasonable to me to allow Promises for transferable values to themselves be transferred, tbh.

@mhofman
Copy link
Member

mhofman commented Jan 20, 2022

In theory promises seem like a good candidate given their shape, but I have a concern: the implementation of cross realm promise resolution is fraught with issues, and there are currently cases where the realm leaks through. I'd be very worried about that complexity poking a hole into the strict callable boundary guarantees.

@leobalter
Copy link
Member

The callable boundary indeed prevents any function returning a non callable object from being wrapped transparently. Async and generator functions are a subset of functions that will indeed always fail when used through the boundary.

In my mind, the current design of the callable boundary is not to be fully transparent, and thus they require the functions being passed across the boundary to be somewhat aware of the boundary primitive constraints.

This is accurate.

I personally want to see a follow up to this proposal with a design that wraps the results of Async and/or Generator functions.

The main use cases where this proposal is being applied to doesn't need that functionality, but I understand the ergonomics of wrapping thenables/promises and iterators can be good to a broader range of usage.

The challenge is how we best design it, so that's why I'd love to explore this on its own pace, while the current implementation allows us to better see what ends we want to expand first. #336 is one of those ends we also need to explore.

@justinfagnani
Copy link

justinfagnani commented Feb 22, 2022

I think this is very important for the SSR use cases. More SSR implementations are moving to streaming by returning a sync or async iterable of strings.

@mhofman
Copy link
Member

mhofman commented Feb 22, 2022

Are promises and iterators return values not something a lightweight user land abstraction could handle for now?

I understand it's not straightforward to implement, but it should be a lot less complex than a full membrane. We only need one library that does this, then we can look into standardizing it.

@caridy caridy added the post-mvp label Feb 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants