From 3040233e7cef4d375f5e8268fe18fb16cc15955e Mon Sep 17 00:00:00 2001 From: galaxy4276 Date: Sun, 19 Apr 2026 23:13:42 +0900 Subject: [PATCH] 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: https://github.com/nodejs/node/issues/62769 --- lib/internal/streams/transform.js | 2 +- .../test-stream-transform-callback-null.js | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-stream-transform-callback-null.js diff --git a/lib/internal/streams/transform.js b/lib/internal/streams/transform.js index 2f4d498bc780a3..a37ec17f9e06cd 100644 --- a/lib/internal/streams/transform.js +++ b/lib/internal/streams/transform.js @@ -174,7 +174,7 @@ Transform.prototype._write = function(chunk, encoding, callback) { return; } - if (val != null) { + if (val !== undefined) { this.push(val); } diff --git a/test/parallel/test-stream-transform-callback-null.js b/test/parallel/test-stream-transform-callback-null.js new file mode 100644 index 00000000000000..889d827066bc78 --- /dev/null +++ b/test/parallel/test-stream-transform-callback-null.js @@ -0,0 +1,66 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Transform } = require('stream'); + +// Passing null as the second argument to the transform callback should be +// equivalent to calling this.push(null), signaling the end of the readable +// side. Refs: https://github.com/nodejs/node/issues/62769 + +{ + // Calling callback(null, null) should end the readable side of the transform stream. + const t = new Transform({ + transform(chunk, encoding, callback) { + callback(null, null); + }, + }); + + t.on('end', common.mustCall()); + t.on('data', (chunk) => { + // Null sentinel should not appear as a data chunk. + assert.fail('unexpected data event'); + }); + + t.write('hello'); + t.end(); +} + +{ + // Verify callback(null, data) still works normally. + const t = new Transform({ + transform(chunk, encoding, callback) { + callback(null, chunk); + }, + }); + + const received = []; + t.on('data', (chunk) => received.push(chunk.toString())); + t.on('end', common.mustCall(() => { + assert.deepStrictEqual(received, ['hello']); + })); + + t.write('hello'); + t.end(); +} + +{ + // Verify callback() with no second arg still works (no push). + const t = new Transform({ + transform(chunk, encoding, callback) { + callback(); + }, + flush(callback) { + callback(null, 'flushed'); + }, + }); + + const received = []; + t.on('data', (chunk) => received.push(chunk.toString())); + t.on('end', common.mustCall(() => { + assert.deepStrictEqual(received, ['flushed']); + })); + + t.write('hello'); + t.end(); +}