Description
Is your feature request related to a problem? Please describe.
The Readable and Writable objects are not well encapsulated: I can inject data into a Readable stream by calling Readable#push
, and I can read data from a Writable stream by monkey-patching Writable#_read
.
Further, these interfaces are not symmetrical. If I want to make data available on a readable side, why not use the write API? This is how it works on every other application: If a client creates a TCP stream, there's one "writable" side, and one "readable" side; yet if I want to make both sides in the same process, I have to use a different API, for some reason.
Describe the solution you'd like
the Node.js stream library should include DuplexPair
and SimplexPair
objects, or equivalent factory functions.
interface SimplexPair {
Readable readable;
Writable writable;
}
interface DuplexPair {
Duplex client;
Duplex server;
}
SimplexPair
SimplexPair creates two different but related objects, one Readable and one Writable; anything written to the Writable side is made available on the Readable side. For encapsulation, the objects would not have public references to each other, and there would be no way to make data available on the readable side without access to the writable side.
DuplexPair
DuplexPair is the same, but both sides are writable and readable.
PassThrough streams
This paradigm should not be new to Node.js developers; a PassThrough stream is just a special case of SimplexPair where both sides are exposed on a single Duplex object.
Transform streams
Transform streams generate two pairs, and returns one from each:
function ROT13Pair(){
const input = new SimplexPair;
const output = new SimplexPair;
input.readable.on('data', function(buf){
output.writable.write(buf.toString().replace(/[a-zA-Z]/g, function(c){
const d = c.charCodeAt(0) + 13;
return String.fromCharCode( ((c<="Z")?90:122)>=d ? d : d-26 );
}));
});
return {
writable: input.writable,
readable: output.readable,
};
}
const { writable, readable } = new ROT13Pair;
process.stdin.pipe(writable);
readable.pipe(process.stdout);
This pattern is repeatable to any level:
function ROT26Pair(){
const input = new ROT13Pair;
const output = new ROT13Pair;
input.readable.pipe(output.writable);
return {
writable: input.writable,
readable: output.readable,
};
}
const { writable, readable } = new ROT26Pair;
process.stdin.pipe(writable);
readable.pipe(process.stdout);
... although I'm not sure how practical this particular example would be in production.
I would bet this style could also result in a modest performance improvement, since much of the logic around buffering and flow control could be re-implemented.