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

Support for "asynchronous exports" #4916

Open
armanbilge opened this issue Dec 2, 2023 · 8 comments
Open

Support for "asynchronous exports" #4916

armanbilge opened this issue Dec 2, 2023 · 8 comments
Labels
language Affects language semantics.

Comments

@armanbilge
Copy link
Member

armanbilge commented Dec 2, 2023

As far as I know there is currently no way to express an export of a value that is computed asynchronously. In ES6 ES2017 ES2022 (sorry, didn't realize how recent it is :) this can be accomplished via a top-level await.

const foo = await Promise.resolve(42);
export { foo };
@sjrd
Copy link
Member

sjrd commented Dec 2, 2023

Ah, I was wondering when someone would request that. 😅 It's kind of challenge to support, actually. I've been thinking about it on and off but did not find a good solution yet.

Do you have a use case at the moment? Or is it hypothetical for you at this point?

@sjrd sjrd added the language Affects language semantics. label Dec 2, 2023
@gzm0
Copy link
Contributor

gzm0 commented Dec 2, 2023

FWIW, we also do not have support for async module initializers (which IMO likely have more use-cases).

@armanbilge
Copy link
Member Author

we also do not have support for async module initializers

Yes, I think this is what I want? What is the difference exactly?

The use case I had in mind falls under the "resource initialization" category described in the original proposal. Most serverless frameworks expect you to export some kind of "handler" function and (at least in our Typelevel.js ecosystem) creating that function typically requires us to do async things e.g. connecting to some servers/databases. Our current workaround is to hide this async initialization in the handler function itself, which is also async, but this is less than ideal.

@sjrd
Copy link
Member

sjrd commented Dec 4, 2023

The difference could be summarized like this:

// asynchronous export:
export let Foo = await makeFoo();

// asynchronous module initialization
await performSideEffectsAsync();

In Scala.js what we call "module initialization" is closer to the user-level concept of "main method". Since we create potentially multiple modules, we can have several "main methods" that are in those separate modules, so they act more like module initializers.

In your case I think you have the "asynchronous export" use case.

Note that in JavaScript these two things are the same. They're both relying on top-level await, not on anything specific to exports or main code. In Scala.js we'll have to differentiate the two, however.

@sjrd
Copy link
Member

sjrd commented Dec 4, 2023

Here is a summary of my least bad idea so far.

Asynchronous export

My best idea would be to introduce a new annotation @JSExportTopLevelAsync, to be used only on static vals as follows:

object Container { // not necessary in Scala 3
  @JSExportTopLevelAsync("foo")
  val foo: js.Promise[Int] = ...
}

This would emit something equivalent to

export const foo = await Container.foo;

Asynchronous module initializer

This is really "least bad" more than "best", but here is what I have.

Add a new kind of ModuleInitializer called asyncMainMethod. It would expect the name of a static method of the form:

def mainMethod(): Any

where the result is expected to be a JavaScript thenable (a js.Promise, basically). The emitted code would be equivalent to

await mainMethod();

Why Any and not js.Promise? Because it shows up in the IR method name, and the IR spec is agnostic of Scala/Scala.js, so we cannot demand that it know about scala.scalajs.js.Promise.

@gzm0
Copy link
Contributor

gzm0 commented Dec 4, 2023

For both of the solutions, have you considered a parameter await instead? (I've also briefly considered always awaiting for module inits, but that's not backwards compatible and is a problematic when not generating ES2022).

@sjrd
Copy link
Member

sjrd commented Dec 4, 2023

I had not considered that. I don't think that's better, though. Boolean flags are usually a code smell, compared to a dedicated name.

(I've also briefly considered always awaiting for module inits, but that's not backwards compatible and is a problematic when not generating ES2022).

Yes, I had the same idea at some point and rejected it for the same reasons.

@gzm0
Copy link
Contributor

gzm0 commented Dec 9, 2023

I don't think that's better, though. Boolean flags are usually a code smell, compared to a dedicated name.

Interesting :) I've not heard that perspective. The way I looked at this is in terms of available combinations: If there are multiple independent options, it's better to express them as boolean flags to avoid combinatorial explosion of names.

For exports there are currently no other options (and its unclear to me whether there will). For module initializers, we can construe the presence of args to be such an option (but indeed we're following the naming pattern there).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
language Affects language semantics.
Projects
None yet
Development

No branches or pull requests

3 participants