Skip to content

Add Symbol.asyncIterator #4967

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

Merged
merged 1 commit into from
Aug 11, 2024
Merged

Conversation

DanielMoss
Copy link
Contributor

Hi!

This symbol returns an object conforming to the async iterator protocol. This protocol is effectively the same as the regular iterator protocol, except the methods on the iterator return values wrapped in promises. Any and all feedback is welcome.

This is a follow-up of my message in the Scala.js discord, where I mentioned I was interested in adding facades for working with the OPFS to scala-js-dom. In particular, Symbol.asyncIterator is required for a complete implementation of the FileSystemDirectoryHandle facade. Assuming I'm going about this the right way, my next step after this PR would be to take a look at adding an AsyncIterator facade to this repo.

Doc links:

@@ -251,6 +252,37 @@ class JSSymbolTest {
assertArrayEquals(Array(532), content.result())
}

@Test def nativeAsyncIterable(): Unit = {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't currently understand the full implications for why the existing equivalent test for Symbol.iterator (i.e. nativeIterable) is implemented the way it is. In particular, it's not clear to me why:

  • the Array.from(iterable) constructor is picked out for testing against both the class and trait facades
  • the iterateIterable-related checks are only performed for the facade trait

Anyway, here's my crack at an equivalent test for Symbol.asyncIterator.

I did take a look at implementing Array.fromAsync(asyncIterable). MDN suggests this feature has support across most browsers already, however its TC39 proposal is still progressing.

Copy link
Member

Choose a reason for hiding this comment

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

The purpose of nativeIterable is to make sure that our mock implementation of the Symbol.iterator protocol corresponds to the spec. We do this by invoking a library function known to be a client of that protocol. That gives us confidence that the sjsdefinedIterable test is actually meaningful. So in a way, nativeIterable tests the other test.

As is, nativeAsyncIterable does not seem to fulfill the same purpose. If there is no Stage-4 library function that we can use as client of the API, we can turn it around. We can use a native provider (probably a js.Array), pass it through our client code iterateAsyncIterable, and check the result. This way we test that iterateAsyncIterable is a correct client of the API. That is also enough to test that sjsdefinedAsyncIterable is testing what it is designed to test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks very much for the detailed explanation. I understand this much better now.

I haven't been able to find any viable alternative clients, and unfortunately, MDN suggests we've a problem with going down the native provider approach too.

There is no object in the core JavaScript language that is async iterable.

It seems to me like we should park this PR until there's a suitable way to validate the protocol behaviour. What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

No, the inability to do a double-test shouldn't hold this PR, IMO. If we don't have any native way to exercise the protocol, let's keep only our user level way of testing it. At least that tests that the symbol actually exists, which at the end of the day is what users can rely on.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cool, I'm happy with that. I've removed the nativeAsyncIterable test now and have left the sjsdefinedAsyncIterable test in place.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I'm late to this. Doesn't an async for loop for await (... consume the async iterator protocol natively?

@gzm0
Copy link
Contributor

gzm0 commented May 20, 2024

@sjrd were you planning on reviewing this? IMO it's more in your area of expertise.

@sjrd
Copy link
Member

sjrd commented May 20, 2024

Ouch yes, I completely missed this one. I'll review tomorrow.

Copy link
Member

@sjrd sjrd left a comment

Choose a reason for hiding this comment

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

Hey @DanielMoss. I'm terribly sorry for the long wait. If you are still interested in seeing this through, here are some comments.

@@ -251,6 +252,37 @@ class JSSymbolTest {
assertArrayEquals(Array(532), content.result())
}

@Test def nativeAsyncIterable(): Unit = {
Copy link
Member

Choose a reason for hiding this comment

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

The purpose of nativeIterable is to make sure that our mock implementation of the Symbol.iterator protocol corresponds to the spec. We do this by invoking a library function known to be a client of that protocol. That gives us confidence that the sjsdefinedIterable test is actually meaningful. So in a way, nativeIterable tests the other test.

As is, nativeAsyncIterable does not seem to fulfill the same purpose. If there is no Stage-4 library function that we can use as client of the API, we can turn it around. We can use a native provider (probably a js.Array), pass it through our client code iterateAsyncIterable, and check the result. This way we test that iterateAsyncIterable is a correct client of the API. That is also enough to test that sjsdefinedAsyncIterable is testing what it is designed to test.

@DanielMoss
Copy link
Contributor Author

Cheers guys. Appreciate the feedback and no problem on the delay. I'll take a look at sorting out the unit test shortly.

@sjrd
Copy link
Member

sjrd commented Jun 19, 2024

The code changes look good now. There are only "administrative" details to get right for the commit history and versioning.

  1. Since this adds a public API, we should bump the minor version. Please include a commit like the following to bump to 1.17.0-SNAPSHOT: 7b1fece (alternatively, cherry-pick 4bf8fa3)
  2. Everything else should be squashed in a single commit with a clean commit message (just "Add Symbol.asyncIterator." is fine in this case).

After that it's good to go.

Thank you for your patience.

@sjrd sjrd force-pushed the symbol-asynciterable branch from 6a7dffa to 4a1e347 Compare August 11, 2024 07:56
Copy link
Member

@sjrd sjrd left a comment

Choose a reason for hiding this comment

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

I took the liberty to rebase+squash, since we now have a commit on main that bumps the version to 1.17.0-SNAPSHOT.

@sjrd sjrd merged commit ede4dc1 into scala-js:main Aug 11, 2024
3 checks passed
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.

3 participants