Skip to content

Commit 8aef7e6

Browse files
galaxy4276claude
andcommitted
stream: allow null as second arg in Transform callback
callback(null, null) in a Transform._transform method should be equivalent to calling this.push(null) followed by callback(), ending the readable side of the stream. Previously val != null blocked the push because null == null is true in loose equality, so push(null) was never called and the stream never emitted 'end'. Change the guard from `val != null` to `val !== undefined` so that null passes through to this.push(), which sets state.ended and eventually emits 'end', matching documented behavior. Fixes: #62769 Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
1 parent 31b9e60 commit 8aef7e6

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

lib/internal/streams/transform.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ Transform.prototype._write = function(chunk, encoding, callback) {
174174
return;
175175
}
176176

177-
if (val != null) {
177+
if (val !== undefined) {
178178
this.push(val);
179179
}
180180

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { Transform } = require('stream');
6+
7+
// Passing null as the second argument to the transform callback should be
8+
// equivalent to calling this.push(null), signaling the end of the readable
9+
// side. Refs: https://github.com/nodejs/node/issues/62769
10+
11+
{
12+
// callback(null, null) should end the readable side of the transform stream.
13+
const t = new Transform({
14+
transform(chunk, encoding, callback) {
15+
callback(null, null);
16+
},
17+
});
18+
19+
t.on('end', common.mustCall());
20+
t.on('data', (chunk) => {
21+
// null sentinel should not appear as a data chunk
22+
assert.fail('unexpected data event');
23+
});
24+
25+
t.write('hello');
26+
t.end();
27+
}
28+
29+
{
30+
// Verify callback(null, data) still works normally.
31+
const t = new Transform({
32+
transform(chunk, encoding, callback) {
33+
callback(null, chunk);
34+
},
35+
});
36+
37+
const received = [];
38+
t.on('data', (chunk) => received.push(chunk.toString()));
39+
t.on('end', common.mustCall(() => {
40+
assert.deepStrictEqual(received, ['hello']);
41+
}));
42+
43+
t.write('hello');
44+
t.end();
45+
}
46+
47+
{
48+
// Verify callback() with no second arg still works (no push).
49+
const t = new Transform({
50+
transform(chunk, encoding, callback) {
51+
callback();
52+
},
53+
flush(callback) {
54+
callback(null, 'flushed');
55+
},
56+
});
57+
58+
const received = [];
59+
t.on('data', (chunk) => received.push(chunk.toString()));
60+
t.on('end', common.mustCall(() => {
61+
assert.deepStrictEqual(received, ['flushed']);
62+
}));
63+
64+
t.write('hello');
65+
t.end();
66+
}

0 commit comments

Comments
 (0)