Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions lib/internal/streams/readable.js
Original file line number Diff line number Diff line change
Expand Up @@ -1522,8 +1522,9 @@ function createAsyncIterator(stream, options) {
const chunk = stream.destroyed ? null : stream.read();
if (chunk !== null) {
// Read `then` only once so that a getter cannot observe (or throw
// on) a second access.
const then = chunk.then;
// on) a second access. `undefined` is a valid chunk value, so it must
// not be dereferenced here.
const then = chunk?.then;
if (typeof then === 'function') {
FunctionPrototypeCall(then, chunk, (value) => {
inFlight = false;
Expand Down Expand Up @@ -1595,8 +1596,9 @@ function createAsyncIterator(stream, options) {
const chunk = stream.destroyed ? null : stream.read();
if (chunk !== null) {
// Read `then` only once so that a getter cannot observe (or
// throw on) a second access.
const then = chunk.then;
// throw on) a second access. `undefined` is a valid chunk value,
// so it must not be dereferenced here.
const then = chunk?.then;
if (typeof then === 'function') {
inFlight = true;
return FunctionPrototypeCall(
Expand Down
38 changes: 38 additions & 0 deletions test/parallel/test-stream-readable-async-iterators.js
Original file line number Diff line number Diff line change
Expand Up @@ -989,5 +989,43 @@ async function tests() {
})().then(common.mustCall());
}

{
// An `undefined` chunk is a value, not end-of-stream. Here it is already
// buffered, so it is read on the synchronous fast path.
(async () => {
const r = new Readable({ objectMode: true, read() {} });
r.push(undefined);
r.push(null);

const it = r[Symbol.asyncIterator]();
assert.deepStrictEqual(await it.next(), { done: false, value: undefined });
assert.strictEqual((await it.next()).done, true);
})().then(common.mustCall());
}

{
// An `undefined` chunk pushed after next() is delivered once it arrives.
(async () => {
const r = new Readable({ objectMode: true, read() {} });
const it = r[Symbol.asyncIterator]();
const next = it.next();
setImmediate(() => {
r.push(undefined);
r.push(null);
});

assert.deepStrictEqual(await next, { done: false, value: undefined });
assert.strictEqual((await it.next()).done, true);
})().then(common.mustCall());
}

{
// Readable.from() delivers `undefined` values.
(async () => {
assert.deepStrictEqual(await Readable.from([undefined]).toArray(),
[undefined]);
})().then(common.mustCall());
}

// To avoid missing some tests if a promise does not resolve
tests().then(common.mustCall());
Loading