sdk icon indicating copy to clipboard operation
sdk copied to clipboard

[dart:js_interop] Support Generators and Iterators

Open nikeokoronkwo opened this issue 8 months ago • 6 comments

JavaScript has the notion of Iterators and Generators, which allow sequential generation and access to elements in a collection without exposing the collection's internal structure.

Some links:

  • function* syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
  • Generator type: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator
  • Iterator protocol: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol

Currently, generating interop for a Generator function would require having to:

  • Create an extension type for the Generator type
  • Write JS interop for the generator function, and possibly lose out on the powerful features it has

This issue aims to address at least one of the following:

  • Adding support for a JSGenerator and JSIterator type: This issue proposes the addition of the JSGenerator extension type and a JSIterator extension type (JSGenerator builds on top of, and extends JSIterator). This allows for not having to unnecessarily reimplement global JS types in different codebases. Instead, it would be better if they were core types in the Dart JS Interop library. Even if JSGenerator isn't viable, having a JSIterator type can help, especially when users may want to use such iterators from JSArray.
  • Add support for mapping JS Generator Functions to Dart Generator Functions: Dart also has a similar notion of Generator functions through the use of sync* and async* mapped with yield and/or yield*
    Iterable<String> list() sync* {
      for (var i = 0; i < 4; i++) {
        yield i.toString();
      }
    }
    
    It would be nice if Generator functions could be mapped from JS to Dart Generator functions (similar to how asynchronous functions in JS are mapped to Dart asynchronous functions). This can help for users to be able to leverage Dart's sound typing when needed (mapping the iterator type in Dart to JSIterator, for instance), and still be able to fall back to JS's way of doing things if they want

nikeokoronkwo avatar Apr 02 '25 12:04 nikeokoronkwo

(similar to how asynchronous functions in JS are mapped to Dart asynchronous functions)

I don't think we actually have this. Do we? There is no automatic conversion between Future and Promise, you need to convert them manually.

Add support for mapping JS Generator Functions to Dart Generator Functions

I think it is worth avoiding confusion about this. Modifiers like sync*, async and async* are modifiers on the implementation (e.g. body of the function), they are not part of the signature. What really matters is the return type. The same applies to function* and async function in JavaScript.

So when you want to expose an async function from JS to Dart it is not about the fact that JS has async function and Dart has async modifier and they are kinda similar. It is simply about converting JS Promise produced by async function when it is called to a Dart Future. (Or in other direction).

The same applies to function* and sync* - it is about converting JS Generator object produced by function* to Dart's Iterable. It is also worth keeping in mind that JS Generator has features which are not in Dart's Iterable (e.g. you can pass values into generator when resuming it after yield, there is support for abruptly stopping generator via either return or throw).` - which might be relevant for interop in the other direction (e.g. binding some JS APIs which accept generators and use them in fancy ways).

mraleph avatar Apr 02 '25 14:04 mraleph

I don't think we actually have this. Do we? There is no automatic conversion between Future and Promise, you need to convert them manually.

Sorry, meant something similar to the manual conversion. There is no automatic conversion.

It is also worth keeping in mind that JS Generator has features which are not in Dart's Iterable (e.g. you can pass values into generator when resuming it after yield, there is support for abruptly stopping generator via either return or throw).

Won't it then be possible to have some Dart implementation for this that extends/implements Iterable that would be a suitable wrapper for the JS Generator iterator type? Then similar properties can be shared between Generator and AsyncGenerator.

nikeokoronkwo avatar Apr 02 '25 17:04 nikeokoronkwo

Won't it then be possible to have some Dart implementation for this that extends/implements Iterable that would be a suitable wrapper for the JS Generator iterator type? Then similar properties can be shared between Generator and AsyncGenerator.

Yep, I think it should be possible to provide Iterable implementation which wraps Generator and Stream implementation that wraps AsyncGenerator.

mraleph avatar Apr 02 '25 17:04 mraleph

Similar request for Iterator: https://github.com/dart-lang/sdk/issues/53532. I believe making JSIterator and JSGenerator generic would allow us to support async iterators and async generators as well using the same JS type definitions (the generic could just be JSPromise<...>), but the conversions to Iterable and Stream may necessitate we have a separate JSAsyncGenerator type instead of conflating the two.

srujzs avatar Apr 05 '25 01:04 srujzs

I believe making JSIterator and JSGenerator generic would allow us to support async iterators and async generators as well using the same JS type definitions (the generic could just be JSPromise<...>) ...but the conversions to Iterable and Stream may necessitate we have a separate JSAsyncGenerator type instead of conflating the two.

I think there is an AsyncGenerator and AsyncIterator type. I don't think there are any extra properties or features to them that would be outside having a generic version of them.

Also, would this mean modifying built in iterables already supported (like String, Array, TypedArray), or would those be better off untouched?

nikeokoronkwo avatar Apr 05 '25 18:04 nikeokoronkwo

Without this type and convertible behavior, I can only use JSArray.from to convert, using JSObject to receive JSIterator.

medz avatar Jun 15 '25 14:06 medz