Files
Matteo Collina c56cb0947f stream: share webstreams async iterator methods
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>
PR-URL: https://github.com/nodejs/node/pull/66392
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
2026-10-05 07:00:09 +00:00

92 lines
1.9 KiB
JavaScript

'use strict';
const common = require('../common.js');
const assert = require('assert');
const {
ReadableStream,
WritableStream,
TransformStream,
} = require('node:stream/web');
const bench = common.createBenchmark(main, {
n: [5e4],
kind: ['readable', 'async-iterator', 'pipe-to', 'pipe-through'],
});
const chunk = Buffer.alloc(1024);
function makeSource() {
let i = 0;
return {
pull(controller) {
if (i++ < 4)
controller.enqueue(chunk);
else
controller.close();
},
};
}
async function readable(n) {
let chunks = 0;
bench.start();
for (let i = 0; i < n; i++) {
const reader = new ReadableStream(makeSource()).getReader();
while (!(await reader.read()).done) chunks++;
}
bench.end(n);
assert.strictEqual(chunks, n * 4);
}
async function asyncIterator(n) {
let chunks = 0;
bench.start();
for (let i = 0; i < n; i++) {
for await (const chunk of new ReadableStream(makeSource())) {
if (chunk) chunks++;
}
}
bench.end(n);
assert.strictEqual(chunks, n * 4);
}
async function pipeTo(n) {
let chunks = 0;
bench.start();
for (let i = 0; i < n; i++) {
await new ReadableStream(makeSource())
.pipeTo(new WritableStream({ write() { chunks++; } }));
}
bench.end(n);
assert.strictEqual(chunks, n * 4);
}
async function pipeThrough(n) {
let chunks = 0;
bench.start();
for (let i = 0; i < n; i++) {
const reader = new ReadableStream(makeSource())
.pipeThrough(new TransformStream())
.getReader();
while (!(await reader.read()).done) chunks++;
}
bench.end(n);
assert.strictEqual(chunks, n * 4);
}
function main({ n, kind }) {
switch (kind) {
case 'readable':
readable(n);
break;
case 'async-iterator':
asyncIterator(n);
break;
case 'pipe-to':
pipeTo(n);
break;
case 'pipe-through':
pipeThrough(n);
break;
}
}