stream: share webstreams async iterator methods - #66392
Conversation
|
Review requested:
|
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>
b436b55 to
6d9a4f3
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #66392 +/- ##
==========================================
+ Coverage 90.36% 90.42% +0.06%
==========================================
Files 792 791 -1
Lines 275498 275616 +118
Branches 52798 52850 +52
==========================================
+ Hits 248947 249225 +278
+ Misses 16976 16780 -196
- Partials 9575 9611 +36
🚀 New features to boost your workflow:
|
Renegade334
left a comment
There was a problem hiding this comment.
This makes the placeholder
AsyncIteratorobject ininternal/webstreams/util.jsunnecessary.
🙏 this was one of our uglier WPT hacks!
WebIDL gives an async iterator prototype the class string "<interface> AsyncIterator", so ReadableStream iterators now report 'ReadableStream AsyncIterator' through Symbol.toStringTag. Signed-off-by: Matteo Collina <hello@matteocollina.com>
|
@Renegade334 ptal |
Commit Queue failedThis pull request has multiple commits, but no landing policy was selected. Add
commit-queue-squash
The pull request was removed from the Commit Queue and labeled
commit-queue-failed
Full Commit Queue output |
|
Landed in c56cb09 |
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.