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

Async iterators #580

Closed
jakearchibald opened this issue Aug 20, 2018 · 8 comments · Fixed by #720
Closed

Async iterators #580

jakearchibald opened this issue Aug 20, 2018 · 8 comments · Fixed by #720
Assignees

Comments

@jakearchibald
Copy link
Contributor

jakearchibald commented Aug 20, 2018

It'd be nice if we could create async iterators in a similar way to iterators.

interface Whatever {
  //
  async_iterable<Something>;
};

The prose would need to be a little more complicated than iterable, probably written like a generator.

For example:

interface SlowCounter {
  //
  async_iterable<unsigned long long>;
};

To asynchronously iterate over a SlowCounter, run the following steps:

  1. Run these steps in parallel:
    1. Let i be 0.
    2. While true:
      1. Wait 1 second.
      2. Yield the value of i using the networking task source.
      3. Increment i by 1.

"Yield" would queue a task on the given task source to resolve the iteration's promise with the given value. The algorithm would not advance until the next iteration was requested.

I guess it's tricky as task sources are an HTML thing, but I can't see how it can be handled correctly otherwise.

@mkruisselbrink
Copy link

So in that proposed syntax if you want to define a method returning an async iterable you'd have to define a separate interface for that return type, and return that instead? Or would async_iterable<Something> also be a valid return type directly? I guess defining the extra interface would be easy enough, just in my mind I was seeing this more as the async equivalent of a method that would otherwise return a sequence<Something>.

But either way, yes, I definitely want some way of being able to return async interables in specs. So whatever shape that takes, sounds good to me.

@jakearchibald
Copy link
Contributor Author

@mkruisselbrink I'm not sure I understand, so this reply may be trash 😀

if you want to define a method returning an async iterable you'd have to define a separate interface for that return type, and return that instead? Or would async_iterable<Something> also be a valid return type directly?

You'd have to define Something, or use an existing type. This isn't a return type, it's more like how iterators are currently defined:

interface interface_identifier {
  iterable<value_type>;
};

But the prose associated with iterable is pretty basic, and I think you'd struggle to write something like fibonacci counter with it (not that it's been a problem as far as I know).

Async iterables are complicated straight away as you need to deal with "in parallel" and task queueing, else there's no point in it being async, so you really need to a way to express how the values are "pulled".

@mkruisselbrink
Copy link

Maybe I'm mixing up async iterables with async iterators. Afaik a interface that specifies iterable<bla> results in several methods being defined on the interface that all return a iterator object (and an iterable can be iterated multiple times, while each iterator object is single use). Would the same be true for async_iterable<bla>? I.e. it would result in a entries() method that returns an async iterator object? I guess what I was thinking of would be being able to define a method that returns an async iterator object directly, rather than having to have an iterable in between. I.e. just define a method that behaves like a async generator function, where I'd expect to have all the in parallel and task queuing steps in the prose/algorithm for my method, rather than having the extra layer/state of an iterable in between.

@inexorabletash
Copy link
Member

Restating:

  • @jakearchibald is proposing an analog for iterable<T> where the interface itself is an iterable
  • @mkruisselbrink is interested in an analog for methods that return sequence<T> which is iterable

In the OP, async_iterable<T> is like iterable<T> but only (so far) defines the [@@asyncIterable]() method, so you get:

let sum = 0;
for await (const i of slowCounter) { sum += i; }

Whereas @mkruisselbrink is after:

let sum = 0;
for await (const i of someObject.getSlowCounter()) { sum += i; }

(Aside: for sync iterable<T> we provide [@@iterator]() as the bare minimum, as well as entries(), values(), keys() and forEach(), following the common methods on Array/Map/Set. So far as I know, we haven't evolved such a pattern for async iterables in the platform.)

With the OP proposal, you'd need to write:

interface Whatever {
  WhateverSomethingAsyncIterator getSomethings();
}
interface WhateverSomethingAsyncIterator {
  async_iterable<Something>;
}

Which, if you're thinking of this as an async replacement for sequence<T> getSomethings() is fairly verbose.

Both use cases seem legitimate. I guess I would assume async_iterable<T> would behave per the OP, and we'd want something like async_sequence<T> for the second use case?

@jakearchibald
Copy link
Contributor Author

@inexorabletash cheers! I'm on the same track now.

Unless I'm missing something, the async_sequence would still need generator-like prose to return items and close the iterator. And with that, you get the problem of task queues vs webidl.

@annevk
Copy link
Member

annevk commented Aug 28, 2018

There is already some intertwinedness, so I wouldn't worry too much about it. Alternative you could define some host hooks that HTML would fill in.

@domenic
Copy link
Member

domenic commented Dec 20, 2018

I'm prototyping something here as part of WICG/kv-storage#6 which should probably get upstreamed eventually. Right now I'm manually pushing stuff into a queue, as does Jake's OP. Interestingly that's not really how async iterators work... they instead "pull", only running a set of steps each time next() is called, lazily. This gives built-in backpressure, which both of us are ignoring, I think. (Unless I misunderstood the "yield" in Jake's OP.)

@jakearchibald
Copy link
Contributor Author

I was thinking "yield" would behave as it does in a generator, so the prose would pause until the next pull.

@Ms2ger Ms2ger self-assigned this Apr 29, 2019
Ms2ger added a commit that referenced this issue May 1, 2019
Ms2ger added a commit that referenced this issue May 23, 2019
Ms2ger added a commit that referenced this issue Jun 26, 2019
Ms2ger added a commit that referenced this issue Jun 26, 2019
Ms2ger added a commit that referenced this issue Aug 7, 2019
Ms2ger added a commit that referenced this issue Aug 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

6 participants