Skip to content

Commit 4010e69

Browse files
jasnelladuh95
authored andcommitted
stream: fix nested async flushing with infinite sources
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode PR-URL: #65658 Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
1 parent 57b6aa4 commit 4010e69

3 files changed

Lines changed: 32 additions & 2 deletions

File tree

doc/api/stream_iter.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,8 @@ added: v25.9.0
545545

546546
Create an async byte stream from the given input. Strings are UTF-8 encoded.
547547
`ArrayBuffer` and `ArrayBufferView` values are wrapped as `Uint8Array`. Arrays
548-
and iterables in `input` are recursively flattened and normalized.
548+
and iterables in `input` are recursively flattened and normalized. Flattened
549+
values may be split across implementation-defined bounded batches.
549550

550551
Objects implementing `Symbol.for('Stream.toAsyncStreamable')` or
551552
`Symbol.for('Stream.toStreamable')` are converted via those protocols. The

lib/internal/streams/iter/from.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,9 +363,13 @@ async function* normalizeAsyncSource(source) {
363363
continue;
364364
}
365365
// Slow path: normalize the value
366-
const batch = [];
366+
let batch = [];
367367
for await (const chunk of normalizeAsyncValue(value)) {
368368
ArrayPrototypePush(batch, chunk);
369+
if (batch.length === FROM_BATCH_SIZE) {
370+
yield batch;
371+
batch = [];
372+
}
369373
}
370374
if (batch.length > 0) {
371375
yield batch;

test/parallel/test-stream-iter-from-async.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,30 @@ async function testFromAsyncGenerator() {
3131
assert.deepStrictEqual(batches[1][0], new Uint8Array([30, 40]));
3232
}
3333

34+
async function testFromBoundsNestedAsyncIterable() {
35+
let nestedClosed = false;
36+
async function* nested() {
37+
try {
38+
let value = 0;
39+
while (true) yield new Uint8Array([value++]);
40+
} finally {
41+
nestedClosed = true;
42+
}
43+
}
44+
45+
async function* source() {
46+
yield nested();
47+
}
48+
49+
const iterator = from(source())[Symbol.asyncIterator]();
50+
const first = await iterator.next();
51+
assert.strictEqual(first.done, false);
52+
assert.strictEqual(first.value.length, 128);
53+
54+
await iterator.return();
55+
assert.strictEqual(nestedClosed, true);
56+
}
57+
3458
async function testFromSyncIterableAsAsync() {
3559
// Sync iterable passed to from() should work
3660
function* gen() {
@@ -274,6 +298,7 @@ function testFromUndefinedThrows() {
274298
Promise.all([
275299
testFromString(),
276300
testFromAsyncGenerator(),
301+
testFromBoundsNestedAsyncIterable(),
277302
testFromSyncIterableAsAsync(),
278303
testFromSyncIterableAwaitsPromiseValues(),
279304
testFromSyncIterableRejectsNestedAsyncIterable(),

0 commit comments

Comments
 (0)