-
Notifications
You must be signed in to change notification settings - Fork 37
Don't fallback to Symbol.iterator in async iteration statement #12
Comments
I think adding Symbol.asyncIterator to the IteratorPrototype would be absolutely necessary if regular iteraters won't work. Also, what if it's a regular iterator that yields promises? |
Then it's not a regular iterator ; ) A regular (synchronous) iterator must return |
Ah! That makes sense. |
What does the async function spec say about using await on non-promises? If it errors out then I think that it would make sense for |
@RangerMauve |
I would argue, then, that if something other than an asyc iterator gets passed in, it should be treated as a regular iterator, but maybe the actual iteration should use microtasks so that it isn't sync. |
Wow, it's been a while since that last comment, but @RangerMauve I think you're right. |
:D Woot! |
I have been working with async iterators in my last project, so I thought I'd add my thoughts. I believe the power of async iterators comes from composing them into larger computations. Each step may do some I/O, for example the first iterator may yield account ids which the second iterator then takes and returns the corresponding row form a database. I have run into cases where a step of the computation can be 100% synchronous, e.g. performing some mathematical transform on values. I'd rather write the synchronous code within the iterator, but from the outside I don't want to give it special treatment. Thus I'd agree with the above. It should be fine to pass in synchronous iterator, but they should behave like the asynchronous ones from the consumer perspective. |
All agreed, so closing : ) |
Reopening due to some concern raised at TC39 about falling back to |
What was the concern specifically? |
About fallback in general, where adding a method to an existing type would change behavior in for-await statements. I just wanted to re-open so that I didn't forget to come back to this later. |
Ha! this looks like a duplicate of the issue I just opened #26. I too want a |
@Blesh There will definitely be a |
I think it should fallback to Symbol.iterator given that we can Take for example the async function *readLines(path) {
let file = await fileOpen(path);
try {
while (!file.EOF)
yield file.readLine();
} finally {
await file.close();
}
}
for await (let line of readLines(path) {
console.log(line);
} should it actually matter if we replace readLines with the synchronous function: function *readLines(path) {
let file = fileOpenSync(path);
try {
while (!file.EOF)
yield file.readline();
} finally {
file.close();
}
} I would say no for the same reason that we allow for awaiting a synchronous value in an asynchronous function, which is essentially that awaiting a synchronous value is not going to change the meaning of a program. This is because we can just treat every synchronous value as an asynchronous value that has already resolved, in a similar vein we should be able to treat an Iterator as a AsyncIterator that will immediately resolve its values. |
How would behavior be different with fallback vs |
@eggers Good question. Once difference would be that anyone wanting to manually implement the Iterator interface (without subclassing the standard iterator) would have to manually implement both |
A point I just thought about is that yield* syncIterator() to yield values differently to this for await (let val of syncIterator()) {
yield val;
} |
Do we actually want to make this change? I thought it was resolved in committee that there should be the fallback. |
The meeting notes seem unclear :( All I see are
I am inclined toward keeping the fallback, although I think it's an interesting idea to move the fallback to Iterator.prototype instead of making it part of the syntax. |
I actually don't recall a consensus being developed on this issue.
The only trouble there is that a user manually implementing an iterator without inheriting from Iterator.prototype would have to explicitly implement Symbol.asyncIterator themselves. |
Yep, as you noted above. I just am not sure that's really a big deal, especially since it's an easy implementation. |
I think we should fall back to The "chaining" semantics of The semantics of Async iterators and generators should work the same way. Just like you can await a value that, accidentally, wasn't a Promise, a reasonable user would expect to be able to I think it would be a big deal to break all of these parallels. It would make async iterators less ergonomic. Let's discuss this the next time async generators/iterators come up on the agenda at TC39. |
I agree with your reasoning, for sure. I think we should make sure that this is encoded in the spec, close this issue, and argue for it in TC39. So the todos are that we need to make sure that not only for-await, but also |
Confirmed that this is handled in the spec via the modifications to GetIterator. They are pretty elegant and centralized so I am happy to go with that instead of |
This fixes the problem discussed in #15 (comment), for both for-await and yield*. It also changes the mechanism by which sync iterator adaptation happens from earlier drafts; before a996238, the adaptation happened in for-await-of by adding an additional await at each step, whereas now, we create an explicit "Async-from-Sync Iterator" adapter. Previously this was discussed around #12 (comment), although there we decided against this option; the recent movements around where unwrapping is performed have made it a better option.
Looking over this again, the behavior where we fallback to using
Symbol.iterator
if the iteration subject does not haveSymbol.asyncIterator
feels hacky.I think it should only look for
Symbol.asyncIterator
. If we want to, we can add async iteration support to regular iterators by adding a method to `%IteratorPrototype%.The text was updated successfully, but these errors were encountered: