Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stream.Writable has no need to inherit from Stream #2961

Closed
ronkorving opened this issue Sep 19, 2015 · 14 comments
Closed

Stream.Writable has no need to inherit from Stream #2961

ronkorving opened this issue Sep 19, 2015 · 14 comments
Labels
semver-major PRs that contain breaking changes and should be released in the next major version. stream Issues and PRs related to the stream subsystem.

Comments

@ronkorving
Copy link
Contributor

Writable currently inherits from Stream without any visible benefit. Stream only exposes pipe() which Writable's own prototype replaces. Would it not be beneficial to make Writable inherit straight from EventEmitter? That way, all EE related API would not need to go one level deeper into the prototype chain, and its constructor would speed up a bit.

I'm asking for feedback, because I'm not sure about drawbacks, or how Duplex may ruin this idea. The only thing that this will break is myWritable instanceof Stream tests. That is BC breakage, but the effects should be very limited I imagine. Also, it doesn't seem to happen anywhere in Node itself.

@ChALkeR
Copy link
Member

ChALkeR commented Sep 19, 2015

but the effects should be very limited I imagine

This will surely break at least some modules.

As always, partial (~⅓) direct usage:

a/anyorm-0.2.13.tgz/lib/service/db.js:800:        var is_stream = (result instanceof Stream);
a/appstackr-0.3.6.tgz/lib/utils.js:612:  return stream instanceof Readable || stream instanceof Stream;
a/archiver-0.14.4.tgz/lib/util/index.js:67:  return source instanceof Stream;
a/adtstream-0.0.4.tgz/src/factory/browser.js:43:    if(toggle instanceof Stream)
a/adtstream-0.0.4.tgz/src/factory/browser.js:55:    if(val instanceof Stream)
a/aliyun-oss-0.6.0.tgz/index.js:175:    if (options.source instanceof Stream) {
a/aliyun-sdk-1.3.10.tgz/lib/param_validator.js:161:      if (ALY.util.Buffer.isBuffer(value) || value instanceof Stream) return;
a/aliyun-sdk-1.3.10.tgz/lib/http/node.js:63:      if (!(body instanceof Stream)) body = this.bufferToStream(body);
a/aliyun-sdk-1.3.10.tgz/lib/http/node.js:67:    if (body instanceof Stream) {
b/browser-builtins-3.3.0.tgz/builtin/_stream_writable.js:113:  if (!(this instanceof Writable) && !(this instanceof Stream.Duplex))
b/bomstream-0.0.8.tgz/lib/Stream.js:341:    if (value instanceof Stream) {
b/bomstream-0.0.8.tgz/test/test-Stream.js:279:                assert(stream instanceof Stream);
b/bomstream-0.0.8.tgz/test/test-Stream.js:314:                    if (err instanceof Stream.Cancelled) {
b/bomstream-0.0.8.tgz/test/test-Stream.js:334:                    if (err instanceof Stream.Cancelled) {
b/bomstream-0.0.8.tgz/test/test-Stream.js:358:                    if (err instanceof Stream.Cancelled) {
b/bomstream-0.0.8.tgz/test/test-Stream.js:374:                    if (!(err instanceof Stream.Cancelled)) {
b/baidu-bcs-0.4.0.tgz/index.js:166:    } else if (options.source instanceof Stream) {
c/combine-streams-1.0.0.tgz/index.js:103:  } else if (obj instanceof Stream) {
c/compress-commons-0.2.9.tgz/lib/util/index.js:14:  return source instanceof Stream;
c/coffea-0.4.5.tgz/index.js:123: * @params {Object} stream      Must be an instanceof StreamReadable and StreamWritable
c/coffea-0.4.5.tgz/index.js:248:    } else if ((typeof info === 'string') || (info instanceof Object && !(info instanceof StreamReadable) && !(info instanceof StreamWritable))) {
c/cwebp-1.0.5.tgz/lib/io.js:43:    done = Buffer.isBuffer(this.source) ? nodefn.call(fs.writeFile, filename, this.source) : this.source instanceof Stream ? ((ref1 = When.defer(), resolve = ref1.resolve, reject = ref1.reject, promise = ref1.promise, ref1), deferred = When.defer(), stream = fs.createWriteStream(filename).once('error', reject).once('close', resolve).once('finish', resolve), this.source.pipe(stream), promise.ensure(function() {
c/cwebp-1.0.5.tgz/lib/io.js:83:    } else if (source instanceof Stream) {
c/chain-of-command-0.0.1.tgz/index.js:46:                stream instanceof Stream.Stream &&
d/dropship-1.0.0.tgz/bundlers/bundler.js:207:    } else if (input instanceof Stream) {
e/express-as-promised-0.1.11.tgz/main.js:70:            if (value instanceof Stream) {
f/freight-0.5.2.tgz/modules/nopt/nopt.js:196:  if (!(val instanceof Stream)) return false
f/frisby-hack-0.8.4.tgz/lib/frisby.js:395:      } else if (!(data instanceof Stream)) {
f/frisby-hack-0.8.4.tgz/lib/frisby.js:471:    if((data instanceof Stream) && (outgoing.method === 'POST' || outgoing.method === 'PUT' || outgoing.method === 'PATCH'))  {
f/fuller-material-file-0.3.0.tgz/index.js:33:   return typeof this.content === "object" && this.content instanceof Stream;
f/f-js-0.5.2.tgz/lib/F.stream.js:331:    if (!(stream instanceof Stream)) {
g/gulp-util-3.0.5.tgz/lib/isStream.js:4:  return !!o && o instanceof Stream;
g/gulp-svg2ttf-1.0.3.tgz/tests/index.mocha.js:164:            assert(objs[0].contents instanceof Stream.PassThrough);
g/gulp-ttf2eot-1.0.1.tgz/tests/index.mocha.js:165:            assert(objs[0].contents instanceof Stream.PassThrough);
g/gulp-svgicons2svgfont-1.2.0.tgz/tests/tests.mocha.js:155:            assert(file.contents instanceof Stream.PassThrough);
g/gulp-svgicons2svgfont-1.2.0.tgz/tests/tests.mocha.js:285:          assert(file.contents instanceof Stream.PassThrough);
g/gulp-php2html-0.2.0.tgz/test/main.js:282:         var isStream = routes instanceof Stream;
g/gulp-ttf2woff-1.0.1.tgz/tests/index.mocha.js:165:            assert(objs[0].contents instanceof Stream.PassThrough);
g/gridfs-locking-stream-1.0.4.tgz/test/index.js:83:      assert(ws instanceof Stream);
g/gridfs-locking-stream-1.0.4.tgz/test/index.js:244:      assert(rs instanceof Stream);
g/gulp-license-1.0.0.tgz/test/streams.js:34:        t.ok(newFile.contents instanceof Stream, 'file contents are a Stream');
g/gm-1.18.1.tgz/index.js:38:  if (source instanceof Stream) {
h/hexo-3.1.1.tgz/lib/hexo/router.js:136:    if (data instanceof Stream && data.readable){
h/hashmark-3.0.0.tgz/index.js:37:        if (contents instanceof Stream.Readable) {
h/hapi-8.6.0.tgz/lib/response.js:80:    else if (source instanceof Stream) {
h/hapi-8.6.0.tgz/lib/response.js:454:    if (source instanceof Stream) {
h/hapi-8.6.0.tgz/lib/response.js:509:    if (stream instanceof Stream) {
i/icedfrisby-0.2.0.tgz/lib/icedfrisby.js:360:      } else if (!(data instanceof Stream)) {
i/icedfrisby-0.2.0.tgz/lib/icedfrisby.js:443:    if((data instanceof Stream) && (outgoing.method === 'POST' || outgoing.method === 'PUT' || outgoing.method === 'PATCH'))  {
i/irrlicht-0.12.1.tgz/lib/irrlicht.js:359:      return response instanceof Stream && response.writable ?
i/iterator-utils-1.0.0.tgz/index.js:73:    case !(iterator.fromStream && a instanceof Stream):
i/image-resizer-1.0.1.tgz/src/image.js:113:  return !!this.contents && this.contents instanceof Stream;
i/icg-gm-1.17.1.tgz/index.js:38:  if (source instanceof Stream) {
i/ieify-1.1.0.tgz/test/index.js:47:    t.ok(ieify() instanceof Stream, '');
j/jsforce-1.4.1.tgz/lib/api/metadata.js:485:  if (zipInput instanceof Stream) {
j/jsforce-1.4.1.tgz/lib/api/bulk.js:351:  if (input instanceof Stream) {
k/kido-agent-1.3.5.tgz/lib/agent.js:360:                    if (!err && data instanceof Stream) {
k/kido-agent-1.3.5.tgz/lib/agent.js:674:        if (data instanceof Stream) {
k/koa-0.21.0.tgz/lib/application.js:214:  if (body instanceof Stream) return body.pipe(res);
k/koa-in-browser-0.3.1.tgz/lib/application.js:216:        //if (body instanceof Stream) return body.pipe(res);
l/lestream-1.0.1.tgz/lib/lestream.js:7:  if (!(this instanceof Stream))
l/line-chomper-0.5.0.tgz/lib/line-chomper.js:275:   if (source instanceof Stream) {
l/lister-1.0.0.tgz/npm/lib/config/defaults.js:36:  if (!(val instanceof Stream)) return false
l/lister-1.0.0.tgz/npm/lib/utils/lifecycle.js:319:    if (value instanceof Stream || Array.isArray(value)) return
m/mini-util-1.1.0.tgz/index.js:114: return value instanceof Stream;
m/morlock-0.4.3.tgz/core/stream.js:14:  if (!(this instanceof Stream)) {
m/musicmetadata-1.0.1.tgz/lib/browser.js:24:  if (file instanceof Stream) {
m/mach-1.3.8.tgz/lib/umd/mach.js:787:       if (value instanceof Stream) {
m/mach-1.3.8.tgz/lib/Message.js:181:    if (value instanceof Stream) {
m/mach-1.3.8.tgz/lib/multipart/parseContent.js:29:    if (!(content instanceof Stream)) content = new Stream(content);
m/mach-1.3.8.tgz/modules/Message.js:182:    if (value instanceof Stream) {
m/mach-1.3.8.tgz/modules/multipart/parseContent.js:27:    if (!(content instanceof Stream))
n/nopt-3.0.2.tgz/lib/nopt.js:170:  if (!(val instanceof Stream)) return false
n/npm-registry-client-6.4.0.tgz/lib/publish.js:51:  assert(body instanceof Stream, 'package body passed to publish must be a stream')
n/npm-registry-client-6.4.0.tgz/lib/request.js:134:    } else if (params.body instanceof Stream) {
n/npm-registry-client-6.4.0.tgz/lib/request.js:154:  if (params.body && (params.body instanceof Stream)) {
n/ndn-js-by-felixrabe-0.4.904.tgz/js/encoding/ASN1/asn1.js:17:    if (enc instanceof Stream) {
n/ndn-js-by-felixrabe-0.4.904.tgz/js/encoding/ASN1/asn1.js:447:    if (!(stream instanceof Stream))
n/nodehaystack-1.0.0.tgz/io/HZincReader.js:77:  if (!(i instanceof Stream.Readable)) {
n/nodehaystack-1.0.0.tgz/io/HJsonReader.js:116:  if (!(input instanceof Stream.Readable)) { // input is our entire string
n/npmconf-2.1.2.tgz/config-defs.js:50:  if (!(val instanceof Stream)) return false
n/nodesite-0.0.1.tgz/vrac/LogStream/index.js:59:        else if( stream.write || stream.read ){ // instanceof Stream.Writable || stream instanceof Stream.Readable ){
n/nodesite-0.0.1.tgz/vrac/ComputedStream/index.js:70:       if( !(stream instanceof Stream) ){
n/npm-2.11.0.tgz/lib/config/defaults.js:36:  if (!(val instanceof Stream)) return false
n/npm-2.11.0.tgz/lib/utils/lifecycle.js:319:    if (value instanceof Stream || Array.isArray(value)) return
n/nnpm-0.0.0.tgz/lib/config/defaults.js:36:  if (!(val instanceof Stream)) return false
n/nnpm-0.0.0.tgz/lib/utils/lifecycle.js:319:    if (value instanceof Stream || Array.isArray(value)) return
n/npm-for-cnpm-2.6.0.tgz/lib/config/defaults.js:42:  if (!(val instanceof Stream)) return false
n/npm-for-cnpm-2.6.0.tgz/lib/utils/lifecycle.js:319:    if (value instanceof Stream || Array.isArray(value)) return
n/nec-sdk-1.0.2.tgz/lib/services/s3.js:211:        if (body instanceof Stream) {
n/nec-sdk-1.0.2.tgz/lib/param_validator.js:161:      if (AWS.util.Buffer.isBuffer(value) || value instanceof Stream) return;
n/nec-sdk-1.0.2.tgz/lib/s3/managed_upload.js:158:      if (self.body instanceof Stream) {
n/nec-sdk-1.0.2.tgz/lib/http/node.js:81:      if (!(body instanceof Stream)) body = AWS.util.buffer.toStream(body);
n/nec-sdk-1.0.2.tgz/lib/http/node.js:85:    if (body instanceof Stream) {
n/naver-npm-3.0.3.tgz/lib/config/defaults.js:36:  if (!(val instanceof Stream)) return false
n/naver-npm-3.0.3.tgz/lib/utils/lifecycle.js:319:    if (value instanceof Stream || Array.isArray(value)) return
o/obj-stream-0.0.23.tgz/lib/stream.js:8:  if (!(this instanceof Stream)) return new Stream();
o/omegapm-0.0.5.tgz/lib/config/defaults.js:42:  if (!(val instanceof Stream)) return false
o/omegapm-0.0.5.tgz/lib/utils/lifecycle.js:319:    if (value instanceof Stream || Array.isArray(value)) return
o/overflow-1.1.2.tgz/overflow.js:10:    if ( !( this instanceof Stream ) ) {
o/oae-rest-11.2.1-1.tgz/lib/util.js:178:                    if (innerValue instanceof Stream) {
o/oae-rest-11.2.1-1.tgz/lib/util.js:193:            if (value instanceof Stream) {
o/oae-rest-11.2.1-1.tgz/lib/util.js:198:        } else if (value instanceof Stream) {
o/openrosa-formlist-1.0.1.tgz/index.js:66:    if (v instanceof Stream) return v;
p/passbook.js-1.0.1.tgz/lib/Manifest.js:28:  else if (contents instanceof Stream) {
p/passbook.js-1.0.1.tgz/lib/Pass.js:72:        !(image instanceof Stream)
p/passbook.js-1.0.1.tgz/lib/Pass.js:82:    if (self.images[name] instanceof Stream) {
p/passbook.js-1.0.1.tgz/lib/Pass.js:275:        if (image instanceof Stream) {
p/pryv-0.1.10.tgz/source/Stream.js:62:    if (p instanceof Stream) {
p/pryv-0.1.10.tgz/source/connection/ConnectionStreams.js:349:      if (!stream || !stream instanceof Stream) {
p/pryv-0.1.10.tgz/source/connection/ConnectionStreams.js:414:      if (!stream || !stream instanceof Stream) {
p/poolee-1.0.0.tgz/lib/request_set.js:34:   if (options.data instanceof Stream) {
p/poolee-1.0.0.tgz/lib/endpoint.js:222:     if (data instanceof Stream) {
p/pp-parachute-1.0.15.tgz/bundlers/bundler.js:208:    } else if (input instanceof Stream) {
p/promise-map-stream-1.0.0.tgz/index.js:23:    if (!(this instanceof Stream)) return new Stream(options)
p/promise-transform-streams-1.0.0.tgz/index.js:17:    if (!(this instanceof Stream)) return new Stream(options)
r/restify-3.0.3.tgz/lib/bunyan_helper.js:25:    if (s instanceof Stream) {
r/revolt-0.9.0.tgz/revolt.js:134:            if (env.request.body instanceof Stream) {
r/revolt-json-parser-0.3.0.tgz/parser.js:29:      if (env.response.body instanceof Stream) {
r/refract-0.3.8.tgz/index.js:140:        if (src instanceof Stream) d.add(src);
r/requirehit-browser-0.1.0.tgz/lib/natives/_stream_readable.js:45:  if (stream instanceof Stream.Duplex)
r/requirehit-browser-0.1.0.tgz/lib/natives/_stream_writable.js:53:  if (stream instanceof Stream.Duplex)
r/requirehit-browser-0.1.0.tgz/lib/natives/_stream_writable.js:153:  if (!(this instanceof Writable) && !(this instanceof Stream.Duplex))
s/s3-buffer-stream-1.0.1.tgz/index.js:16:  if (!(this instanceof Stream)) return new Stream(options)
s/soniwave-1.0.5.tgz/index.js:20:  if (source instanceof Stream) {
s/servmix-0.0.3.tgz/lib/utils.js:40:    return !!o && o instanceof Stream;
s/studiolite-1.1.116.tgz/_common/_js/jsencrypt/jsencrypt.js:3322:    if (enc instanceof Stream) {
s/studiolite-1.1.116.tgz/_common/_js/jsencrypt/jsencrypt.js:3768:    if (!(stream instanceof Stream))
s/s3-streams-0.2.0.tgz/test/spec/read.js:38:        if (obj instanceof Stream.Readable || obj instanceof Stream.Writable) {
s/streamfilter-1.0.1.tgz/src/index.js:8:  if(!(this instanceof StreamFilter)) {
t/toa-0.10.0.tgz/test/response/socket.js:18:    assert(res.socket instanceof Stream);
t/toa-0.10.0.tgz/index.js:205:    if (body instanceof Stream) body.once('error', ctx.onerror);
t/toa-0.10.0.tgz/index.js:221:  } else if (body instanceof Stream) {
t/through3-1.1.5.tgz/lib/through.js:32:    if(!(this instanceof Stream)) {
t/tiny-request-2.0.0.tgz/index.js:52:      if (source instanceof Stream) {
v/vinyl-transform-1.0.0.tgz/test.js:78:    t.ok(file.contents instanceof Stream, 'file.contents is a stream')
v/vinyl-transform-1.0.0.tgz/test.js:175:      t.ok(file.contents instanceof Stream, 'file.contents is still a stream')
v/vinyl-0.4.6.tgz/lib/isStream.js:4:  return !!o && o instanceof Stream;
w/wyvern-1.2.52.tgz/system/cartridge/AssetsCartridge.js:277:    if (!(stream instanceof Stream)) {
w/wintersmith-2.2.0.tgz/lib/core/renderer.js:68:        } else if (result instanceof Stream || result instanceof Buffer) {
w/wintersmith-2.2.0.tgz/lib/core/renderer.js:73:          if (result instanceof Stream) {
w/wintersmith-2.2.0.tgz/lib/core/server.js:328:                if (result instanceof Stream) {
w/wspp-0.7.1.tgz/lib/Stream.js:30:  if (!(this instanceof Stream))
w/websocket-rpc-stream-0.0.15.tgz/src/stream.js:9:    if (!(this instanceof Stream)) {
w/wreck-5.5.1.tgz/lib/index.js:44:        options.payload instanceof Stream || Buffer.isBuffer(options.payload),
w/wreck-5.5.1.tgz/lib/index.js:170:        if (options.payload instanceof Stream) {
w/wreck-5.5.1.tgz/test/index.js:1485:        expect(stream instanceof Stream).to.be.true;
w/wreck-5.5.1.tgz/test/index.js:1496:        expect(stream instanceof Stream).to.be.true;
y/yapm-2.4.2.tgz/lib/config/defaults.js:42:  if (!(val instanceof Stream)) return false
y/yapm-2.4.2.tgz/lib/utils/lifecycle.js:319:    if (value instanceof Stream || Array.isArray(value)) return
y/yahoo-finance-stream-0.1.2.tgz/index.js:11:  if (!(this instanceof Stream)) {
z/zombie-4.0.10.tgz/lib/fetch.js:305:    } else if (bodyInit instanceof Stream.Readable) {
z/zip-stream-0.5.2.tgz/lib/util/index.js:77:  return source instanceof Stream;

@brendanashworth brendanashworth added the stream Issues and PRs related to the stream subsystem. label Sep 19, 2015
@Slayer95
Copy link

Agreed with @ChALkeR .

It should still be possible to prevent the breakage by using Symbol.hasInstance, right?
(When its support lands in V8)

@reqshark
Copy link

@ronkorving, the same could be said for Readable, since it also replaces Stream's pipe().

I suspect inheriting from the Stream super is a holdover from the Streams 1, a.k.a. pre 0.10 era. Before node 0.10.x, there were no separate Readable and Writable base classes. I don't think instanceof Stream tests are anything substantive aside from targeting historic versions of node.

Moreover Stream has a circular prototypal reference, require('stream').Stream, these things are useless markers left from the past, like the human appendix in the lower right abdomen.

@ronkorving
Copy link
Contributor Author

@ChALkeR out of interest, how do you generate that list?

@ChALkeR
Copy link
Member

ChALkeR commented Sep 20, 2015

@ronkorving With grep on some locally downloaded modules =). I will share this with instructions (and will update it recent version of all packages) when I will have a bit more free time. Maybe in a few weeks. I'm thinking of making an web api for that.

@ronkorving
Copy link
Contributor Author

Cool :)

@chrisdickinson
Copy link
Contributor

Writable inherits from Stream so that writable streams that Node generates and hands to userland will still instanceof Stream correctly. Since instantiation makes up only a tiny bit of the stream's overall lifetime, I'm not sure it's worth optimizing, given the risk of breaking downstream modules. However, a good first step would be to put up benchmark numbers that show that inheriting directly from EE leads to an appreciable speedup of a real world program, like a simple HTTP server.

It's worth mentioning that streams1 streams aren't gone, or slated for deprecation — even in the post-streams2 world it's totally valid to create a streams1 stream, especially if you need to implement a different buffering strategy than what streams2+ provides.

@ronkorving
Copy link
Contributor Author

To me, this was as much about elegance and cleanup (if not more) than about performance. I really doubt that in the grand scope of things you would be able to measure anything (as long as I/O is involved).

@rvagg
Copy link
Member

rvagg commented Sep 21, 2015

It's been a fairly safe assumption that instanceof Stream should just work, except if you're doing this in the browser of course where everything's terrible. This has carried over from streams1 until now and it's not something we should break lightly. I see no good reason to do so now.

I didn't realise until I looked it up just now but isstream is kind of well used:

isstream

and relies on instanceof, on my insistence because duck-typing isn't enough to determine if a stream is strictly a Node.js stream and not a stream-like, tho others disagree. I'd rather see isstreamlike for that case, or isstream.isStreamLike()

also, @nodejs/streams needs to be in on this discussion, it's theirs to decide anyway

@calvinmetcalf
Copy link
Contributor

I think the analogy to the appendix is good in the sense that it seems like a useless holdover from the past at first glance, but in fact is not.

The ability to check if something is an instance of a stream might not be the best way if we were starting all over again from scratch, but it would cause a large amount of backwards compatibility to change and any improvement changing it would create would be minor compared with the issues it caused.

@isaacs
Copy link
Contributor

isaacs commented Sep 21, 2015

Maintaining backwards compatibility is a visible benefit for those of us using node heavily in production systems. In general, it makes upgrading easier, and keeps the community from splitting unnecessarily.

I don't think this breakage is the worst, but it WOULD be a cost, and one with little to no tangible benefit. (Code gets a little bit prettier, but not faster or more featureful.)

-1 from me, but if I'm happy to admit I might be overlooking some aspect of the decision.

@michaelnisi
Copy link

It has semantic value and could become useful again in the future. I'd say leave it in, however, it would be interesting to see if the performance impact of this is measurable.

@ronkorving
Copy link
Contributor Author

@michaelnisi That's the $64,000 question (boy, I'm getting old): is there any reason to believe it will become useful again in the future? Its existence may be a result of code smell to begin with. However, this speculation is no reason to remove the Stream class. If there's even a remote chance of it becoming useful again in the future, that is a -1.

@ChALkeR ChALkeR added the semver-major PRs that contain breaking changes and should be released in the next major version. label Oct 5, 2015
@ronkorving
Copy link
Contributor Author

Closing this. Don't see this ever happening, and the benefits are way too limited.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver-major PRs that contain breaking changes and should be released in the next major version. stream Issues and PRs related to the stream subsystem.
Projects
None yet
Development

No branches or pull requests

10 participants