-
Notifications
You must be signed in to change notification settings - Fork 29.8k
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: proper instanceof
for Writable
s
#8834
Conversation
Use `[Symbol.hasInstance]()` to return `true` when asking for `new Duplex() instanceof Writable`.
@@ -1559,7 +1559,8 @@ Because JavaScript does not have support for multiple inheritance, the | |||
to extending the `stream.Readable` *and* `stream.Writable` classes). | |||
|
|||
*Note*: The `stream.Duplex` class prototypically inherits from `stream.Readable` | |||
and parasitically from `stream.Writable`. | |||
and parasitically from `stream.Writable`, but `instanceof` will work properly | |||
for both base classes by overriding [`Symbol.hasInstance`][] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@italoacasas done :)
@@ -1559,7 +1559,8 @@ Because JavaScript does not have support for multiple inheritance, the | |||
to extending the `stream.Readable` *and* `stream.Writable` classes). | |||
|
|||
*Note*: The `stream.Duplex` class prototypically inherits from `stream.Readable` | |||
and parasitically from `stream.Writable`. | |||
and parasitically from `stream.Writable`, but `instanceof` will work properly | |||
for both base classes by overriding [`Symbol.hasInstance`][]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
due to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You’re the native speaker, so I’m going to go with that. ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this PR can be slimmed down a bit, but I might be wrong.
I think that this is semver-minor, but we might discuss if it's semver-major instead.
// Test _writableState for inheritance to account for Duplex streams, | ||
// whose prototype chain only points to Readable. | ||
var realHasInstance; | ||
if (Symbol.hasInstance) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should feature-detect that Symbol
exists to avoid a regexp in readable-stream
. Some of our supported platforms there do not have symbols yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, right. Done!
if (!(this instanceof Writable) && !(this instanceof Stream.Duplex)) | ||
// Writable ctor is applied to Duplexes, too. | ||
if (!(realHasInstance.call(Writable, this)) && | ||
!(this instanceof Stream.Duplex)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to change this block? I think we can probably leave as it was before here.
As I understand this PR, all the awesomeness is implemented by overriding Writable.hasInstance
.
So, can we remove realHasInstance
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaving as it is will lead to infinite recursion, because this instanceof Writable
returns false
before the constructor is run, so the constructor just calls itself over and over again. And adding an extra realHasInstance
check inside of the symbol-based override would break our fancy LazyTransform
logic.
Feel free to play around with this, but I am afraid this would be necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about we solve this by adding a check on Writable.hasInstance
like: Function.prototype[Symbol.hasInstance].call(Writable, this) || this._writableState instanceof WritableState
? I think that will reduce our cruft.
I would like to avoid adding one more variable to support older node variables :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that will reduce our cruft.
@mcollina Yeah, me too, so I tried it – that’s what breaks LazyTransform
, because it invokes the Writable
constructor from inside a _writableState
getter. :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a comment for that, because it is definitely not obvious.
@calvinmetcalf what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’ve added comments that hopefully clarify things a bit. :)
BTW, I did not know this was even possible. Good job @addaleax. |
@mcollina updated, ptal! |
I'm LGTM, but we should wait @calvinmetcalf. |
I think this is semver-minor. |
I would really like to have another @nodejs/streams folk review this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
nice!
lgtm |
👍 Thanks for making this compatible with old node versions for readable-stream as well. |
(Just noting we should be careful not to promote streams instanceof checks outside of core as they'll break with modules using readable-stream) |
Just to be clear, this isn’t going to work on older node versions, unfortunately; it’s just not going to crash. (I assume you know that but it may not be obvious to everyone). |
for the record |
Maybe it's worth having similar |
@bengl yes. But they will still be useless. readable-stream has a completely different inheritance chain, so users cannot expect to instanceof Writable (or Duplex) and also check if it comes from readble-stream. This change will be useful when interacting with core or core-provided streams. |
I’d like to land this on Monday if there are no objections. |
I'd like to get this into v6.8.0 |
I don’t think there are any reasons not to land this now, so I’ll do that? |
Landed in 2a4b068 |
Use `[Symbol.hasInstance]()` to return `true` when asking for `new Duplex() instanceof Writable`. PR-URL: #8834 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Calvin Metcalf <[email protected]>
Use `[Symbol.hasInstance]()` to return `true` when asking for `new Duplex() instanceof Writable`. PR-URL: #8834 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Calvin Metcalf <[email protected]>
Use `[Symbol.hasInstance]()` to return `true` when asking for `new Duplex() instanceof Writable`. PR-URL: #8834 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Calvin Metcalf <[email protected]>
* fs: - `SyncWriteStream` now inherits from `Stream.Writable`. (Anna Henningsen) #8830 - Practically, this means that when stdio is piped to a file, stdout and stderr will still be `Writable` streams. - `fs.existsSync()` has been undeprecated. `fs.exists()` remains deprecated. (Dan Fabulich) #8364 * http: `http.request()` now accepts a `timeout` option. (Rene Weber) #8101 * module: The module loader now maintains its own realpath cache. (Anna Henningsen) #8100 * npm: Upgraded to 3.10.8 (Kat Marchán) #8706 * stream: `Duplex` streams now show proper `instanceof Stream.Writable`. (Anna Henningsen) #8834 * timers: Improved `setTimeout`/`Interval` performance by up to 22%. (Brian White) #8661 PR-URL: #9034
* fs: - `SyncWriteStream` now inherits from `Stream.Writable`. (Anna Henningsen) #8830 - Practically, this means that when stdio is piped to a file, stdout and stderr will still be `Writable` streams. - `fs.existsSync()` has been undeprecated. `fs.exists()` remains deprecated. (Dan Fabulich) #8364 * http: `http.request()` now accepts a `timeout` option. (Rene Weber) #8101 * module: The module loader now maintains its own realpath cache. (Anna Henningsen) #8100 * npm: Upgraded to 3.10.8 (Kat Marchán) #8706 * stream: `Duplex` streams now show proper `instanceof Stream.Writable`. (Anna Henningsen) #8834 * timers: Improved `setTimeout`/`Interval` performance by up to 22%. (Brian White) #8661 PR-URL: #9034
FYI this breaks Babel when trying to create a class that extends import { Writable } from 'stream'
class Example extends Writable {}
export default new Example() Compiled: 'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _stream = require('stream');
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var Example = function (_Writable) {
_inherits(Example, _Writable);
function Example() {
_classCallCheck(this, Example);
return _possibleConstructorReturn(this, (Example.__proto__ || Object.getPrototypeOf(Example)).apply(this, arguments));
}
return Example;
}(_stream.Writable);
exports.default = new Example(); |
Sounds like a bug? |
I fear we merged this too early. This is the error:
|
A note to add: const Writable = require('stream').Writable
class Example extends Writable {}
new Example() Does not error. The error happens because of what @addaleax and myself discussed here: https://github.com/nodejs/node/pull/8834/files#diff-1915a7b992a3012b177dd855fa3477e0R144. I think we should revert this asap. Babel is on everyone's desk. As a side note, I think this is the polyfill fault, and it is not semver-major. Node.js is not responsible of the code generated by the transpiler, as the code works smoothly with the language itself. @jcready can you please suggest a babelified library that we can put under CITGM? |
I think this might be an actual bug in my PR and I think I know where that comes from… should have a PR up in a few minutes. Sorry for the trouble. |
2a4b068 introduced a regression in where checking `instanceof` would fail for `Writable` subclasses inside the subclass constructor, i.e. before `Writable()` was called. Also, calling `null instanceof Writable` or `undefined instanceof Writable` would fail due to accessing the `_writableState` property of the target object. This fixes these problems. Ref: nodejs#8834 (comment)
Proposed fix: #9088 |
* fs: - `SyncWriteStream` now inherits from `Stream.Writable`. (Anna Henningsen) nodejs/node#8830 - Practically, this means that when stdio is piped to a file, stdout and stderr will still be `Writable` streams. - `fs.existsSync()` has been undeprecated. `fs.exists()` remains deprecated. (Dan Fabulich) nodejs/node#8364 * http: `http.request()` now accepts a `timeout` option. (Rene Weber) nodejs/node#8101 * module: The module loader now maintains its own realpath cache. (Anna Henningsen) nodejs/node#8100 * npm: Upgraded to 3.10.8 (Kat Marchan) nodejs/node#8706 * stream: `Duplex` streams now show proper `instanceof Stream.Writable`. (Anna Henningsen) nodejs/node#8834 * timers: Improved `setTimeout`/`Interval` performance by up to 22%. (Brian White) nodejs/node#8661 Signed-off-by: Ilkka Myller <[email protected]>
* fs: - `SyncWriteStream` now inherits from `Stream.Writable`. (Anna Henningsen) nodejs/node#8830 - Practically, this means that when stdio is piped to a file, stdout and stderr will still be `Writable` streams. - `fs.existsSync()` has been undeprecated. `fs.exists()` remains deprecated. (Dan Fabulich) nodejs/node#8364 * http: `http.request()` now accepts a `timeout` option. (Rene Weber) nodejs/node#8101 * module: The module loader now maintains its own realpath cache. (Anna Henningsen) nodejs/node#8100 * npm: Upgraded to 3.10.8 (Kat Marchan) nodejs/node#8706 * stream: `Duplex` streams now show proper `instanceof Stream.Writable`. (Anna Henningsen) nodejs/node#8834 * timers: Improved `setTimeout`/`Interval` performance by up to 22%. (Brian White) nodejs/node#8661 Signed-off-by: Ilkka Myller <[email protected]>
2a4b068 introduced a regression in where checking `instanceof` would fail for `Writable` subclasses inside the subclass constructor, i.e. before `Writable()` was called. Also, calling `null instanceof Writable` or `undefined instanceof Writable` would fail due to accessing the `_writableState` property of the target object. This fixes these problems. PR-URL: #9088 Ref: #8834 (comment) Reviewed-By: Ilkka Myller <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
2a4b068 introduced a regression in where checking `instanceof` would fail for `Writable` subclasses inside the subclass constructor, i.e. before `Writable()` was called. Also, calling `null instanceof Writable` or `undefined instanceof Writable` would fail due to accessing the `_writableState` property of the target object. This fixes these problems. PR-URL: #9088 Ref: #8834 (comment) Reviewed-By: Ilkka Myller <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
2a4b068 introduced a regression in where checking `instanceof` would fail for `Writable` subclasses inside the subclass constructor, i.e. before `Writable()` was called. Also, calling `null instanceof Writable` or `undefined instanceof Writable` would fail due to accessing the `_writableState` property of the target object. This fixes these problems. PR-URL: #9088 Ref: #8834 (comment) Reviewed-By: Ilkka Myller <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
2a4b068 introduced a regression in where checking `instanceof` would fail for `Writable` subclasses inside the subclass constructor, i.e. before `Writable()` was called. Also, calling `null instanceof Writable` or `undefined instanceof Writable` would fail due to accessing the `_writableState` property of the target object. This fixes these problems. PR-URL: #9088 Ref: #8834 (comment) Reviewed-By: Ilkka Myller <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
Checklist
make -j8 test
(UNIX), orvcbuild test nosign
(Windows) passesAffected core subsystem(s)
streams
Description of change
Use
[Symbol.hasInstance]()
to returntrue
when asking fornew Duplex() instanceof Writable
.Ref: #8830 (comment)
/cc @nodejs/streams