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

Sync-to-async iterable conversion has more guarantees than plain async iterable conversion #3417

Open
domfarolino opened this issue Sep 5, 2024 · 0 comments

Comments

@domfarolino
Copy link
Member

GetIterator(obj, ASYNC) returns an Iterator Record with a [[NextMethod]] that is pulled from the user-supplied object. The next method hopefully returns a Promise, but this requirement is not enforced. Because it is not enforced, specs that convert values to async iterators this way must take care to wrap the resulting value in a Promise. This is standard practice; the Streams Standard does this, and so do the semantics of for await loops: specifically, the head evaluation semantics grab an async iterator record, and then step 6 of the body evaluation gets the next value and unconditionally Await()s it. Await() first wraps the value in a Promise and awaits it, which is necessary in case the value returned from the author's async iterable's next() method is not a Promise.

In other words, the consumer of an async iterable is responsible for wrapping all next() values in a Promise, in case the code author did not do this. This is understandable, but a little annoying I guess, since responsibility is pushed to the consumer when ECMAScript itself could take care of this.

In fact, ECMAScript does take care of this (i.e., ensuring that all next() values are Promises) but only when GetIterator(obj, ASYNC) falls back to the @@iterator implementation (i.e., when @@asyncIterator is not present). In this case and this case only, the fallback prose doesn't just directly use the sync iterator Record for its next() values; instead, it delegates to CreateAsyncFromSyncIterator(), which creates an internal %AsyncFromSyncIteratorPrototype% object whose next() method is guaranteed to return a Promise that resolves to the underlying [[SyncIteratorRecord]]'s actual next value (which should be an Iterator Result).

This automatic Promise-wrapping is quite nice, but it's strange that we only do it for the least kind of async iterable, i.e., sync iterables. Since we have this automatic Promise-wrapping semantics sometimes, it'd be nice if we could extend it to all converted async iterables, so that consumers of GetIterator(obj, ASYNC) could always guarantee that the result of IteratorNext() is a Promise.

If we don't, then since consumers of async iterables are anyways trained to do the wrapping themselves, maybe we can just get rid of the CreateAsyncFromSyncIterator() usage in the sync fallback case. After all, there's no need for [[NextMethod]] to always return a Promise, since consumers can't rely on this in general.

Thoughts?

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

No branches or pull requests

1 participant