From e5f6fef83876c447ffab704d8617d3719f73c238 Mon Sep 17 00:00:00 2001 From: Robert Jefe Lindstaedt Date: Mon, 2 May 2016 07:03:23 +0200 Subject: [PATCH 1/6] doc: discourage use of util.inherits; point to es6 extends util.inherits breaks the prototype chain. A fix does not seem useful, since ES6 extends provides language level support for the same functionality. This commit starts fasing out mentions of the method. Fixes: #4179 --- doc/api/events.md | 23 +-- doc/api/stream.md | 408 +++++++++++++++++++++++----------------------- doc/api/util.md | 4 + 3 files changed, 209 insertions(+), 226 deletions(-) diff --git a/doc/api/events.md b/doc/api/events.md index f608e3e859a557..eb826a62c2bd17 100644 --- a/doc/api/events.md +++ b/doc/api/events.md @@ -29,12 +29,8 @@ the `eventEmitter.emit()` method is used to trigger the event. ```js const EventEmitter = require('events'); -const util = require('util'); -function MyEmitter() { - EventEmitter.call(this); -} -util.inherits(MyEmitter, EventEmitter); +class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); myEmitter.on('event', () => { @@ -44,21 +40,8 @@ myEmitter.emit('event'); ``` Any object can become an `EventEmitter` through inheritance. The example above -uses the traditional Node.js style prototypical inheritance using -the `util.inherits()` method. It is, however, possible to use ES6 classes as -well: - -```js -const EventEmitter = require('events'); - -class MyEmitter extends EventEmitter {} - -const myEmitter = new MyEmitter(); -myEmitter.on('event', () => { - console.log('an event occurred!'); -}); -myEmitter.emit('event'); -``` +uses the ES6 classes. It is however possible to use traditional Node.js style +prototypical inheritance using the `util.inherits()` method. ## Passing arguments and `this` to listeners diff --git a/doc/api/stream.md b/doc/api/stream.md index 83fb13d71e55c7..3e06a8a13566cf 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -730,8 +730,7 @@ the [`'drain'`][] event before writing more data. To implement any sort of stream, the pattern is the same: -1. Extend the appropriate parent class in your own subclass. (The - [`util.inherits()`][] method is particularly helpful for this.) +1. Extend the appropriate parent class in your own subclass via ES6 `extends`. 2. Call the appropriate parent class constructor in your constructor, to be sure that the internal mechanisms are set up properly. 3. Implement one or more specific methods, as detailed below. @@ -934,31 +933,31 @@ could wrap the low-level source object by doing something like this: // and an `ondata` member that gets called when it has data, and // an `onend` member that gets called when the data is over. -util.inherits(SourceWrapper, Readable); -function SourceWrapper(options) { - Readable.call(this, options); +class SourceWrapper extends Readable { + constructor(options) { + super(options); - this._source = getLowlevelSourceObject(); + this._source = getLowlevelSourceObject(); - // Every time there's data, we push it into the internal buffer. - this._source.ondata = (chunk) => { - // if push() returns false, then we need to stop reading from source - if (!this.push(chunk)) - this._source.readStop(); - }; + // Every time there's data, we push it into the internal buffer. + this._source.ondata = (chunk) => { + // if push() returns false, then we need to stop reading from source + if (!this.push(chunk)) + this._source.readStop(); + }; - // When the source ends, we push the EOF-signaling `null` chunk - this._source.onend = () => { - this.push(null); + // When the source ends, we push the EOF-signaling `null` chunk + this._source.onend = () => { + this.push(null); + }; + } + // _read will be called when the stream wants to pull more data in + // the advisory size argument is ignored in this case. + _read(size) { + this._source.readStart(); }; } - -// _read will be called when the stream wants to pull more data in -// the advisory size argument is ignored in this case. -SourceWrapper.prototype._read = function(size) { - this._source.readStart(); -}; ``` #### Example: A Counting Stream @@ -970,25 +969,25 @@ from 1 to 1,000,000 in ascending order, and then ends. ```js const Readable = require('stream').Readable; -const util = require('util'); -util.inherits(Counter, Readable); - -function Counter(opt) { - Readable.call(this, opt); - this._max = 1000000; - this._index = 1; -} -Counter.prototype._read = function() { - var i = this._index++; - if (i > this._max) - this.push(null); - else { - var str = '' + i; - var buf = Buffer.from(str, 'ascii'); - this.push(buf); +class Counter extends Readable { + constructor(opt) { + super(opt); + this._max = 1000000; + this._index = 1; } -}; + + _read() { + var i = this._index++; + if (i > this._max) + this.push(null); + else { + var str = '' + i; + var buf = Buffer.from(str, 'ascii'); + this.push(buf); + } + }; +} ``` #### Example: SimpleProtocol v1 (Sub-optimal) @@ -1011,94 +1010,92 @@ However, this would be better implemented as a [Transform][] stream. See // alternative example below under the Transform section. const Readable = require('stream').Readable; -const util = require('util'); -util.inherits(SimpleProtocol, Readable); +class SimpleProtocol extends Readable { + constructor(source, options) { + super(options); + if (!(this instanceof SimpleProtocol)) + return new SimpleProtocol(source, options); -function SimpleProtocol(source, options) { - if (!(this instanceof SimpleProtocol)) - return new SimpleProtocol(source, options); + this._inBody = false; + this._sawFirstCr = false; - Readable.call(this, options); - this._inBody = false; - this._sawFirstCr = false; + // source is a readable stream, such as a socket or file + this._source = source; - // source is a readable stream, such as a socket or file - this._source = source; + source.on('end', () => { + this.push(null); + }); - source.on('end', () => { - this.push(null); - }); + // give it a kick whenever the source is readable + // read(0) will not consume any bytes + source.on('readable', () => { + this.read(0); + }); - // give it a kick whenever the source is readable - // read(0) will not consume any bytes - source.on('readable', () => { - this.read(0); - }); - - this._rawHeader = []; - this.header = null; -} + this._rawHeader = []; + this.header = null; + } -SimpleProtocol.prototype._read = function(n) { - if (!this._inBody) { - var chunk = this._source.read(); - - // if the source doesn't have data, we don't have data yet. - if (chunk === null) - return this.push(''); - - // check if the chunk has a \n\n - var split = -1; - for (var i = 0; i < chunk.length; i++) { - if (chunk[i] === 10) { // '\n' - if (this._sawFirstCr) { - split = i; - break; + _read(n) { + if (!this._inBody) { + var chunk = this._source.read(); + + // if the source doesn't have data, we don't have data yet. + if (chunk === null) + return this.push(''); + + // check if the chunk has a \n\n + var split = -1; + for (var i = 0; i < chunk.length; i++) { + if (chunk[i] === 10) { // '\n' + if (this._sawFirstCr) { + split = i; + break; + } else { + this._sawFirstCr = true; + } } else { - this._sawFirstCr = true; + this._sawFirstCr = false; } - } else { - this._sawFirstCr = false; } - } - if (split === -1) { - // still waiting for the \n\n - // stash the chunk, and try again. - this._rawHeader.push(chunk); - this.push(''); - } else { - this._inBody = true; - var h = chunk.slice(0, split); - this._rawHeader.push(h); - var header = Buffer.concat(this._rawHeader).toString(); - try { - this.header = JSON.parse(header); - } catch (er) { - this.emit('error', new Error('invalid simple protocol data')); - return; + if (split === -1) { + // still waiting for the \n\n + // stash the chunk, and try again. + this._rawHeader.push(chunk); + this.push(''); + } else { + this._inBody = true; + var h = chunk.slice(0, split); + this._rawHeader.push(h); + var header = Buffer.concat(this._rawHeader).toString(); + try { + this.header = JSON.parse(header); + } catch (er) { + this.emit('error', new Error('invalid simple protocol data')); + return; + } + // now, because we got some extra data, unshift the rest + // back into the read queue so that our consumer will see it. + var b = chunk.slice(split); + this.unshift(b); + // calling unshift by itself does not reset the reading state + // of the stream; since we're inside _read, doing an additional + // push('') will reset the state appropriately. + this.push(''); + + // and let them know that we are done parsing the header. + this.emit('header', this.header); } - // now, because we got some extra data, unshift the rest - // back into the read queue so that our consumer will see it. - var b = chunk.slice(split); - this.unshift(b); - // calling unshift by itself does not reset the reading state - // of the stream; since we're inside _read, doing an additional - // push('') will reset the state appropriately. - this.push(''); - - // and let them know that we are done parsing the header. - this.emit('header', this.header); + } else { + // from there on, just provide the data to our consumer. + // careful not to push(null), since that would indicate EOF. + var chunk = this._source.read(); + if (chunk) this.push(chunk); } - } else { - // from there on, just provide the data to our consumer. - // careful not to push(null), since that would indicate EOF. - var chunk = this._source.read(); - if (chunk) this.push(chunk); - } -}; - + }; +} // Usage: // var parser = new SimpleProtocol(source); // Now parser is a readable stream that will emit 'header' @@ -1231,65 +1228,66 @@ would be piped into the parser, which is a more idiomatic Node.js stream approach. ```javascript -const util = require('util'); const Transform = require('stream').Transform; -util.inherits(SimpleProtocol, Transform); -function SimpleProtocol(options) { - if (!(this instanceof SimpleProtocol)) - return new SimpleProtocol(options); +class SimpleProtocol extends Transform { + constructor(options) { + super(options); + if (!(this instanceof SimpleProtocol)) + return new SimpleProtocol(options); - Transform.call(this, options); - this._inBody = false; - this._sawFirstCr = false; - this._rawHeader = []; - this.header = null; -} + this._inBody = false; + this._sawFirstCr = false; + this._rawHeader = []; + this.header = null; + } -SimpleProtocol.prototype._transform = function(chunk, encoding, done) { - if (!this._inBody) { - // check if the chunk has a \n\n - var split = -1; - for (var i = 0; i < chunk.length; i++) { - if (chunk[i] === 10) { // '\n' - if (this._sawFirstCr) { - split = i; - break; + _transform(chunk, encoding, done) { + if (!this._inBody) { + // check if the chunk has a \n\n + var split = -1; + for (var i = 0; i < chunk.length; i++) { + if (chunk[i] === 10) { // '\n' + if (this._sawFirstCr) { + split = i; + break; + } else { + this._sawFirstCr = true; + } } else { - this._sawFirstCr = true; + this._sawFirstCr = false; } - } else { - this._sawFirstCr = false; } - } - if (split === -1) { - // still waiting for the \n\n - // stash the chunk, and try again. - this._rawHeader.push(chunk); - } else { - this._inBody = true; - var h = chunk.slice(0, split); - this._rawHeader.push(h); - var header = Buffer.concat(this._rawHeader).toString(); - try { - this.header = JSON.parse(header); - } catch (er) { - this.emit('error', new Error('invalid simple protocol data')); - return; - } - // and let them know that we are done parsing the header. - this.emit('header', this.header); + if (split === -1) { + // still waiting for the \n\n + // stash the chunk, and try again. + this._rawHeader.push(chunk); + } else { + this._inBody = true; + var h = chunk.slice(0, split); + this._rawHeader.push(h); + var header = Buffer.concat(this._rawHeader).toString(); + try { + this.header = JSON.parse(header); + } catch (er) { + this.emit('error', new Error('invalid simple protocol data')); + return; + } + // and let them know that we are done parsing the header. + this.emit('header', this.header); - // now, because we got some extra data, emit this first. - this.push(chunk.slice(split)); + // now, because we got some extra data, emit this first. + this.push(chunk.slice(split)); + } + } else { + // from there on, just provide the data to our consumer as-is. + this.push(chunk); } - } else { - // from there on, just provide the data to our consumer as-is. - this.push(chunk); - } - done(); -}; + done(); + }; +} + // Usage: // var parser = new SimpleProtocol(); @@ -1625,57 +1623,56 @@ respectively. These options can be used to implement parsers and serializers with Transform streams. ```js -const util = require('util'); const StringDecoder = require('string_decoder').StringDecoder; const Transform = require('stream').Transform; -util.inherits(JSONParseStream, Transform); // Gets \n-delimited JSON string data, and emits the parsed objects -function JSONParseStream() { - if (!(this instanceof JSONParseStream)) - return new JSONParseStream(); - - Transform.call(this, { readableObjectMode : true }); - - this._buffer = ''; - this._decoder = new StringDecoder('utf8'); -} +class JSONParseStream extends Transform { + constructor() { + super({ readableObjectMode : true }); + if (!(this instanceof JSONParseStream)) + return new JSONParseStream(); + + this._buffer = ''; + this._decoder = new StringDecoder('utf8'); + } -JSONParseStream.prototype._transform = function(chunk, encoding, cb) { - this._buffer += this._decoder.write(chunk); - // split on newlines - var lines = this._buffer.split(/\r?\n/); - // keep the last partial line buffered - this._buffer = lines.pop(); - for (var l = 0; l < lines.length; l++) { - var line = lines[l]; - try { - var obj = JSON.parse(line); - } catch (er) { - this.emit('error', er); - return; + _transform(chunk, encoding, cb) { + this._buffer += this._decoder.write(chunk); + // split on newlines + var lines = this._buffer.split(/\r?\n/); + // keep the last partial line buffered + this._buffer = lines.pop(); + for (var l = 0; l < lines.length; l++) { + var line = lines[l]; + try { + var obj = JSON.parse(line); + } catch (er) { + this.emit('error', er); + return; + } + // push the parsed object out to the readable consumer + this.push(obj); } - // push the parsed object out to the readable consumer - this.push(obj); - } - cb(); -}; + cb(); + }; -JSONParseStream.prototype._flush = function(cb) { - // Just handle any leftover - var rem = this._buffer.trim(); - if (rem) { - try { - var obj = JSON.parse(rem); - } catch (er) { - this.emit('error', er); - return; + _flush(cb) { + // Just handle any leftover + var rem = this._buffer.trim(); + if (rem) { + try { + var obj = JSON.parse(rem); + } catch (er) { + this.emit('error', er); + return; + } + // push the parsed object out to the readable consumer + this.push(obj); } - // push the parsed object out to the readable consumer - this.push(obj); - } - cb(); -}; + cb(); + }; +} ``` ### `stream.read(0)` @@ -1728,7 +1725,6 @@ horribly wrong. [`stream.unpipe()`]: #stream_readable_unpipe_destination [`stream.wrap()`]: #stream_readable_wrap_stream [`tls.CryptoStream`]: tls.html#tls_class_cryptostream -[`util.inherits()`]: util.html#util_util_inherits_constructor_superconstructor [API for Stream Consumers]: #stream_api_for_stream_consumers [API for Stream Implementors]: #stream_api_for_stream_implementors [child process stdin]: child_process.html#child_process_child_stdin diff --git a/doc/api/util.md b/doc/api/util.md index d298ae4b44425a..94f5fd2d325968 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -126,6 +126,10 @@ util.format(1, 2, 3); // '1 2 3' ## util.inherits(constructor, superConstructor) +_Note: usage of `util.inherits()` is discouraged. Please use ES6 `class` and +`extends` to get language level inheritance support, including an unbroken +inheritance chain._ + Inherit the prototype methods from one [constructor][] into another. The prototype of `constructor` will be set to a new object created from `superConstructor`. From 28332bbc0426386037f6934b26edf9aa6a3f9c25 Mon Sep 17 00:00:00 2001 From: Robert Jefe Lindstaedt Date: Mon, 2 May 2016 15:23:34 +0200 Subject: [PATCH 2/6] doc: util.inherits removal, address review --- doc/api/events.md | 4 ---- doc/api/stream.md | 15 +++++++-------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/doc/api/events.md b/doc/api/events.md index eb826a62c2bd17..577f4abfe25efd 100644 --- a/doc/api/events.md +++ b/doc/api/events.md @@ -39,10 +39,6 @@ myEmitter.on('event', () => { myEmitter.emit('event'); ``` -Any object can become an `EventEmitter` through inheritance. The example above -uses the ES6 classes. It is however possible to use traditional Node.js style -prototypical inheritance using the `util.inherits()` method. - ## Passing arguments and `this` to listeners The `eventEmitter.emit()` method allows an arbitrary set of arguments to be diff --git a/doc/api/stream.md b/doc/api/stream.md index 3e06a8a13566cf..3851823f368ce5 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -730,7 +730,8 @@ the [`'drain'`][] event before writing more data. To implement any sort of stream, the pattern is the same: -1. Extend the appropriate parent class in your own subclass via ES6 `extends`. +1. Extend the appropriate parent class in your own subclass via the `extends` + keyword. 2. Call the appropriate parent class constructor in your constructor, to be sure that the internal mechanisms are set up properly. 3. Implement one or more specific methods, as detailed below. @@ -956,7 +957,7 @@ class SourceWrapper extends Readable { // the advisory size argument is ignored in this case. _read(size) { this._source.readStart(); - }; + } } ``` @@ -1094,7 +1095,7 @@ class SimpleProtocol extends Readable { var chunk = this._source.read(); if (chunk) this.push(chunk); } - }; + } } // Usage: // var parser = new SimpleProtocol(source); @@ -1285,10 +1286,8 @@ class SimpleProtocol extends Transform { this.push(chunk); } done(); - }; + } } - - // Usage: // var parser = new SimpleProtocol(); // source.pipe(parser) @@ -1655,7 +1654,7 @@ class JSONParseStream extends Transform { this.push(obj); } cb(); - }; + } _flush(cb) { // Just handle any leftover @@ -1671,7 +1670,7 @@ class JSONParseStream extends Transform { this.push(obj); } cb(); - }; + } } ``` From e97064dbb212bbb717e9903d8e835140ed783380 Mon Sep 17 00:00:00 2001 From: Robert Jefe Lindstaedt Date: Mon, 2 May 2016 15:33:53 +0200 Subject: [PATCH 3/6] doc: util.inherits removal, remove no-new-if-checks --- doc/api/stream.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/doc/api/stream.md b/doc/api/stream.md index 3851823f368ce5..c0d0d9d833768e 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -1015,8 +1015,6 @@ const Readable = require('stream').Readable; class SimpleProtocol extends Readable { constructor(source, options) { super(options); - if (!(this instanceof SimpleProtocol)) - return new SimpleProtocol(source, options); this._inBody = false; this._sawFirstCr = false; @@ -1234,8 +1232,6 @@ const Transform = require('stream').Transform; class SimpleProtocol extends Transform { constructor(options) { super(options); - if (!(this instanceof SimpleProtocol)) - return new SimpleProtocol(options); this._inBody = false; this._sawFirstCr = false; @@ -1629,8 +1625,6 @@ const Transform = require('stream').Transform; class JSONParseStream extends Transform { constructor() { super({ readableObjectMode : true }); - if (!(this instanceof JSONParseStream)) - return new JSONParseStream(); this._buffer = ''; this._decoder = new StringDecoder('utf8'); From 339f823a9b164e60f143706449785d0614e48190 Mon Sep 17 00:00:00 2001 From: Robert Jefe Lindstaedt Date: Mon, 2 May 2016 18:23:53 +0200 Subject: [PATCH 4/6] doc: util.inherits removal, missed semicolon --- doc/api/stream.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/stream.md b/doc/api/stream.md index c0d0d9d833768e..7daf1eda93bae8 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -987,7 +987,7 @@ class Counter extends Readable { var buf = Buffer.from(str, 'ascii'); this.push(buf); } - }; + } } ``` From c6d6a1dda415b9bb5a1384a441005a0e2524ef86 Mon Sep 17 00:00:00 2001 From: Robert Jefe Lindstaedt Date: Tue, 3 May 2016 14:57:29 +0200 Subject: [PATCH 5/6] doc: util.inherits removal, note incompatiblity --- doc/api/util.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/util.md b/doc/api/util.md index 94f5fd2d325968..d8e7183bc7bbd3 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -126,9 +126,9 @@ util.format(1, 2, 3); // '1 2 3' ## util.inherits(constructor, superConstructor) -_Note: usage of `util.inherits()` is discouraged. Please use ES6 `class` and -`extends` to get language level inheritance support, including an unbroken -inheritance chain._ +_Note: usage of util.inherits() is discouraged. Please use the ES6 `class` and +`extends` keywords to get language level inheritance support. Also note that +the two styles are semantically incompatible._ Inherit the prototype methods from one [constructor][] into another. The prototype of `constructor` will be set to a new object created from From 06441f4163f65aa6b60188d3a40315b8fd466b26 Mon Sep 17 00:00:00 2001 From: Robert Jefe Lindstaedt Date: Sat, 7 May 2016 01:01:47 +0200 Subject: [PATCH 6/6] doc: util.inherits removal: link to github issue for incompat --- doc/api/util.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/api/util.md b/doc/api/util.md index d8e7183bc7bbd3..92d434ebcf2218 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -128,7 +128,7 @@ util.format(1, 2, 3); // '1 2 3' _Note: usage of util.inherits() is discouraged. Please use the ES6 `class` and `extends` keywords to get language level inheritance support. Also note that -the two styles are semantically incompatible._ +the two styles are [semantically incompatible][]._ Inherit the prototype methods from one [constructor][] into another. The prototype of `constructor` will be set to a new object created from @@ -585,6 +585,7 @@ similar built-in functionality through `Object.assign`. [`Array.isArray`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray [constructor]: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/constructor +[semantically incompatible]: https://github.com/nodejs/node/issues/4179 [Customizing `util.inspect` colors]: #util_customizing_util_inspect_colors [here]: #util_customizing_util_inspect_colors [`Error`]: errors.html#errors_class_error