Stage: 1
Champions: Michael Ficarra
Authors: Michael Ficarra
An early form of the iterator helpers MVP proposal included variants of each iterator helper MVP method on AsyncIterator.prototype
. These methods were specified to match the behaviour of a naive generator-based implementation, with the philosophy that reasoning about their behaviour would be as simple as saying "what would this do in the first implementation that comes to mind?". This unfortunately meant that the async iterator helper variants adopted the undesirable queueing behaviour of generators: no matter how many times the produced async iterator was next()
-ed without waiting for outstanding promises to settle, the underlying iterator would never have more than one outstanding next()
. The high-level consequence of this is that async iterators that supported (and could benefit from) concurrent next()
s would lose their support when transformed by any of the transformation methods provided by the iterator helpers MVP. To buy ourselves time to resolve this issue without holding up sync iterator helpers, we split async iterator helpers out into their own proposal. Note: I am very grateful to the community members who got involved and forced us to think harder about this, spend extra time, and not just do the easy thing!
Later, when working on the async iterator helpers proposal and trying to maximise the concurrency that we could permit with the async iterator MVP methods, we discovered that dropping the ordering constraint allowed for far more efficient use of the available concurrency. It's also expected that many use cases (possibly most) will be able to drop the ordering constraint. Unordered helpers were briefly explored as part of the async iterator helpers proposal, but because the design space was large, and in order to not hold up that proposal, they were deferred to a later proposal, in the same way async iterator helpers were deferred from the sync iterator helpers proposal. This is that deferred proposal.
The async iterator helpers proposal introduces a .toAsync()
method on Iterator.prototype
to lift the iterator to an async iterator with AsyncIterator.prototype
as its prototype. AsyncIterator.prototype
has all of the same method names as Iterator.prototype
. This proposal takes a similar approach.
AsyncIterator.prototype
gets a method named .unordered()
to lift an async iterator into an unordered async iterator with UnorderedAsyncIterator.prototype
as its prototype. UnorderedAsyncIterator.prototype
has all the same method names as Iterator.prototype
and AsyncIterator.prototype
(aside from .toAsync()
and .unordered()
probably?), except their implementations are able to achieve much higher concurrency by possibly resolving vended Promises in an order other than the order they were vended.
We can see a visualisation of the throughput difference between ordered and unordered transforms from alexpusch/rust-magic-patterns/rust-stream-visualized.
Because unordered helpers provide no advantage in the absence of concurrent calls to .next()
, the iterator-consuming methods in this proposal (.some()
, .forEach()
, etc) will depend on the concurrency control proposal.
- should the names of
.toAsync()
and.unordered()
match better?AsyncIterator.prototype.toUnordered()
?Iterator.prototype.async()
?
- should UnorderedAsyncIterator.prototype have a
.unordered()
method?- likewise, should {Unordered,}AsyncIterator.prototype have a
.toAsync()
?
- likewise, should {Unordered,}AsyncIterator.prototype have a
- require the concurrency parameter?
- concurrency of 1 is basically like calling an ordered helper (bad)
This is worse because it permits chaining order-preserving helpers after unordered helpers, losing the concurrency advantage. The naming also favours ordered helpers, even though unordered helpers should be encouraged wherever possible.