Conversation
Collaborator
|
Review requested:
|
mcollina
marked this pull request as ready for review
September 29, 2026 07:52
ReadableStream.prototype.values() built each iterator from an object literal with a computed symbol-key method plus five closures. Such a literal is rebuilt through the runtime on every evaluation, costing close to a microsecond per iterator, which dominates iterating a short-lived stream. Move next() and return() to a shared ReadableStreamAsyncIterator prototype, as for any WebIDL async iterator, and keep the per-iterator state in its read request. The prototype chain and property shape are the ones WPT checks; the placeholder AsyncIterator object in util.js is no longer needed. As in WebIDL, next() and return() now reject when called on something that is not a ReadableStream async iterator, and iterators no longer carry own next/return properties. Add an async-iterator kind to benchmark/webstreams/lifecycle.js. webstreams/lifecycle.js kind='async-iterator' *** +16.22% Signed-off-by: Matteo Collina <hello@matteocollina.com>
mcollina
force-pushed
the
webstream-perf-round20
branch
from
September 29, 2026 07:55
b436b55 to
6d9a4f3
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #66392 +/- ##
=======================================
Coverage 90.36% 90.37%
=======================================
Files 792 792
Lines 275498 275514 +16
Branches 52798 52810 +12
=======================================
+ Hits 248947 248982 +35
+ Misses 16976 16947 -29
- Partials 9575 9585 +10
🚀 New features to boost your workflow:
|
Renegade334
reviewed
Sep 29, 2026
Renegade334
left a comment
Member
There was a problem hiding this comment.
This makes the placeholder
AsyncIteratorobject ininternal/webstreams/util.jsunnecessary.
🙏 this was one of our uglier WPT hacks!
| // rather than being created per iterator. Neither method may be an async | ||
| // function: `await` goes through Promise.prototype.then, which the streams | ||
| // WPTs patch. | ||
| class ReadableStreamAsyncIterator { |
Member
There was a problem hiding this comment.
Pedantically, this prototype should have a toStringTag of 'ReadableStream AsyncIterator'.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Round 20 of the webstreams performance work (follows #66230). It targets
for awaitover a short-lived stream, such as iterating a response body once per request.Async iterator methods on a shared prototype
ReadableStream.prototype.values()built every iterator from an object literal holdingnext(),return()and a computed[Symbol.asyncIterator]()method, plus closures for the next and return steps. A literal with a computed symbol-key method goes through the runtime on every evaluation, the same cost #66154 removed from pipeTo and tee. That cost close to a microsecond per iterator.next()andreturn()now live on a sharedReadableStreamAsyncIteratorprototype, which is how WebIDL defines async iterators. Each iterator holds only a private reference to its state, and that state doubles as the iterator's read request. The prototype extends%AsyncIteratorPrototype%and has exactly thenextandreturnproperties WPT checks for. This makes the placeholderAsyncIteratorobject ininternal/webstreams/util.jsunnecessary. The return steps are no longer an async function either: the cancel path chains the result onto the cancel promise, which settles one microtask later exactly asawaitdid. Microtask timing is unchanged, including the extra hop before the first read.Observable differences
Both follow WebIDL and match browsers:
next/returnproperties (Object.keys(stream.values())was['next', 'return'], now[]);next()andreturn()return a promise rejected withERR_INVALID_THISwhen called on something that is not a ReadableStream async iterator, for example afterconst { next } = iterator.test/parallel/test-whatwg-readablestream-async-iterator-shape.jscovers both.Benchmark
benchmark/webstreams/lifecycle.jsgains anasync-iteratorkind (create a stream, read 4 chunks withfor await).node benchmark/compare.js --runs 20(lifecycle, readable-async-iterator, readable-read):The long-running iterator rows are flat as expected, since the saving is per iterator, not per chunk. With a source that enqueues its chunks in
start(), iterator creation is a larger share of the work and a local harness measured +70 %.A 26-scenario microtask-ordering stress for async iteration logs identically against
main. It covers concurrentnext()calls,return()with a read pending, slow, rejecting and throwingcancel(),break, errors,preventCanceland byte streams. WPT streams and the webstreams parallel batch are green.AI generated, humanly reviewed.