From fb8cc72e738f2854302bf270b2f3997bc273b9a6 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Sun, 25 Aug 2019 18:13:27 +0200 Subject: [PATCH] stream: construct Provide a standardized way of asynchronously creating and initializing resources before performing any work. Refs: https://github.com/nodejs/node/issues/29314 PR-URL: https://github.com/nodejs/node/pull/29656 Reviewed-By: Matteo Collina Reviewed-By: Anna Henningsen Reviewed-By: Denys Otrishko --- doc/api/stream.md | 171 ++++++++++++++++- lib/_stream_readable.js | 24 ++- lib/_stream_writable.js | 27 ++- lib/internal/streams/destroy.js | 98 +++++++++- test/parallel/test-stream-construct.js | 244 +++++++++++++++++++++++++ 5 files changed, 544 insertions(+), 20 deletions(-) create mode 100644 test/parallel/test-stream-construct.js diff --git a/doc/api/stream.md b/doc/api/stream.md index 6dda48f9a25d64..e8a3050291e9c3 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -550,8 +550,7 @@ added: v9.3.0 * {number} -Return the value of `highWaterMark` passed when constructing this -`Writable`. +Return the value of `highWaterMark` passed when creating this `Writable`. ##### `writable.writableLength` -For many simple cases, it is possible to construct a stream without relying on +For many simple cases, it is possible to create a stream without relying on inheritance. This can be accomplished by directly creating instances of the `stream.Writable`, `stream.Readable`, `stream.Duplex` or `stream.Transform` objects and passing appropriate methods as constructor options. @@ -1801,8 +1799,14 @@ objects and passing appropriate methods as constructor options. const { Writable } = require('stream'); const myWritable = new Writable({ + construct(callback) { + // Initialize state and load resources... + }, write(chunk, encoding, callback) { // ... + }, + destroy() { + // Free resources... } }); ``` @@ -1861,6 +1865,8 @@ changes: [`stream._destroy()`][writable-_destroy] method. * `final` {Function} Implementation for the [`stream._final()`][stream-_final] method. + * `construct` {Function} Implementation for the + [`stream._construct()`][writable-_construct] method. * `autoDestroy` {boolean} Whether this stream should automatically call `.destroy()` on itself after ending. **Default:** `true`. @@ -1906,6 +1912,56 @@ const myWritable = new Writable({ }); ``` +#### `writable._construct(callback)` + + +* `callback` {Function} Call this function (optionally with an error + argument) when the stream has finished initializing. + +The `_construct()` method MUST NOT be called directly. It may be implemented +by child classes, and if so, will be called by the internal `Writable` +class methods only. + +This optional function will be called in a tick after the stream constructor +has returned, delaying any `_write`, `_final` and `_destroy` calls until +`callback` is called. This is useful to initialize state or asynchronously +initialize resources before the stream can be used. + +```js +const { Writable } = require('stream'); +const fs = require('fs'); + +class WriteStream extends Writable { + constructor(filename) { + super(); + this.filename = filename; + this.fd = fd; + } + _construct(callback) { + fs.open(this.filename, (fd, err) => { + if (err) { + callback(err); + } else { + this.fd = fd; + callback(); + } + }); + } + _write(chunk, encoding, callback) { + fs.write(this.fd, chunk, callback); + } + _destroy(err, callback) { + if (this.fd) { + fs.close(this.fd, (er) => callback(er || err)); + } else { + callback(err); + } + } +} +``` + #### `writable._write(chunk, encoding, callback)` + +* `callback` {Function} Call this function (optionally with an error + argument) when the stream has finished initializing. + +The `_construct()` method MUST NOT be called directly. It may be implemented +by child classes, and if so, will be called by the internal `Readable` +class methods only. + +This optional function will be called by the stream constructor, +delaying any `_read` and `_destroy` calls until `callback` is called. This is +useful to initialize state or asynchronously initialize resources before the +stream can be used. + +```js +const { Readable } = require('stream'); +const fs = require('fs'); + +class ReadStream extends Readable { + constructor(filename) { + super(); + this.filename = filename; + this.fd = null; + } + _construct(callback) { + fs.open(this.filename, (fd, err) => { + if (err) { + callback(err); + } else { + this.fd = fd; + callback(); + } + }); + } + _read(n) { + const buf = Buffer.alloc(n); + fs.read(this.fd, buf, 0, n, null, (err, bytesRead) => { + if (err) { + this.destroy(err); + } else { + this.push(bytesRead > 0 ? buf.slice(0, bytesRead) : null); + } + }); + } + _destroy(err, callback) { + if (this.fd) { + fs.close(this.fd, (er) => callback(er || err)); + } else { + callback(err); + } + } +} +``` + #### `readable._read(size)`