stream-flow-control
Version:
Stream Flow Control
142 lines (127 loc) • 4.74 kB
JavaScript
const {Writable, Readable} = require('stream');
const {Manager} = require('../manager/manager.js');
/**
* Stream every element of an array individually.
*
* @extends Writable
*/
class FlowEach extends Writable {
/**
* Create a FlowEach stream
* @param {object} [options] Global options
* @param {string} [options.name] Name for this stream
*/
constructor(options) {
options = {...options, objectMode: true};
super(options);
this.options = options;
this.type = 'FlowEach';
if(this.options.name) Manager.set(this.type, this);
this._reader = null;
this._corking_readers = {};
this._reader_messages = [];
this._none_reader = null;
}
/**
* Set stream(s) to pipe to
* @param {(Writable|Writable[])} dst
* @returns {(Writable|FlowAll)} if dst is an array, then returns "this", else dst is returned
*/
pipe(dst) {
if(!this._reader) {
const self = this;
this._reader = new Readable({
objectMode: true,
read() {
if(self._writableState.corked && self._corking_readers['reader']) {
self.uncork();
delete self._corking_readers['reader'];
self._deliver_messages();
}
}
});
this._reader.options = {...this._reader.options||{}, name: this.options.name};
this._reader._readableState.sync = false;
}
const pipes = Array.isArray(dst)?dst:[dst];
pipes.forEach(dst=>this._reader.pipe(dst));
return Array.isArray(dst)?this:dst;
}
/**
* Set stream(s) to pipe to when no other stream was piped
* @param {(Writable|Writable[])} dst Destination stream
* @returns {(Writable|FlowAll)} if dst is an array, then returns "this", else dst is returned
*/
none(dst) {
if(!this._none_reader) {
const self = this;
this._none_reader = new Readable({
objectMode: true,
read() {
if(self._writableState.corked && self._corking_readers['reader']) {
self.uncork();
delete self._corking_readers['reader'];
}
}
});
this._none_reader.options = {...this._none_reader.options||{}, name: this.options.name, port: 'none'};
this._none_reader._readableState.sync = false;
}
const pipes = Array.isArray(dst)?dst:[dst];
pipes.forEach(dst=>this._none_reader.pipe(dst));
return Array.isArray(dst)?this:dst;
}
_deliver_messages() {
if(this._reader) {
while(this._reader_messages.length) {
if(!this._reader.push(this._reader_messages.shift()) && !this._corking_readers['reader']) {
this.cork();
this._corking_readers['reader'] = true;
break;
}
}
}
if(!this._reader_messages.length && this._needsEnd) {
this._reader && this._reader.push(null);
this._none_reader && this._none_reader.push(null);
}
}
_write(payload, encoding, cb) {
let data;
if(this.goal) {
// Is goal player
data = payload.data;
} else {
data = payload;
}
if(this._reader) {
this._reader_messages = this._reader_messages.concat((!Array.isArray(data)?[data]:data));
if(this.goal) {
this._reader_messages = this._reader_messages.map(data=>payload.getChild(this).setData(data));
};
this._deliver_messages();
}
if(!this._reader && this._none_reader) {
if(this.options.none_reason) {
let data = typeof this.options.none_reason == 'function'?this.options.none_reason(payload):this.options.none_reason;
payload = (this.goal)?payload.getChild(this).setData(data):data;
} else {
payload = this.goal?payload.getChild(this):payload
}
if(!this._none_reader.push(payload) && !this._corking_readers['none']) {
this.cork();
this._corking_readers['none'] = true;
}
}
cb();
}
end() {
if(!this._reader_messages.length) {
this._reader && this._reader.push(null);
this._none_reader && this._none_reader.push(null);
} else {
this._needsEnd = true;
}
}
}
module.exports.FlowEach = FlowEach;