stream-flow-control
Version:
Stream Flow Control
196 lines (181 loc) • 6.55 kB
JavaScript
const {Writable, Readable} = require('stream');
const {Manager} = require('../manager/manager.js');
const {DataWrapper} = require('../wrapper/data_wrapper.js');
const vm = require('vm');
/**
* Goal wrapps many streams within, hidding complex business logic. Represents a goal to achieve.
*
* @extends Writable
*/
class Goal extends Writable {
/**
* Create a Goal stream
* @param {object} [options] Global options
* @param {string} [options.name] Name for this stream
*/
constructor(options, cb) {
options = {...options, objectMode: true};
super(options);
this.options = options;
this.type = 'Goal';
if(this.options.name) Manager.set(this.type, this);
const self = this;
this._start = new Readable({
objectMode: true,
read() {
self.uncork();
}
});
this._start.options = {...this._start.options||{}, name: this.options.name, port: 'start'};
this._start._readableState.sync = false;
this.on('prefinish', ()=>{
this._start.push(null);
})
this._error_reader = new Readable({
objectMode: true,
read() {}
});
this._error_reader.options = {...this._error_reader.options||{}, name: this.options.name, port: 'error'};
this._error_reader._readableState.sync = false;
this._resolve_reader = null;
this._reject_reader = null;
this._resolve_writer =new Writable({
objectMode: true,
write: (payload, encoding, cb) => {
let data = payload instanceof DataWrapper?payload.data:payload;
if(self._cb) self._cb(null, data);
if(self._resolve_reader) {
//process.nextTick(()=>self._resolve_reader.push(data));
let goOn = self._resolve_reader.push(data);
if(!self._resolve_writer.corked && !goOn) self._resolve_writer.cork();
}
self.emit('resolve', data);
cb();
}
});
this._resolve_writer.on('prefinish', ()=>{
if(this._resolve_reader) this._resolve_reader.push(null);
});
this._reject_writer = new Writable({
objectMode: true,
write: (payload, encoding, cb) => {
// TODO - save payload for statistics
let data = payload instanceof DataWrapper?payload.data:payload;
if(self._cb) self._cb(data);
if(self._reject_reader) {
//process.nextTick(()=>self._resolve_reader.push(data));
let goOn = self._reject_reader.push(data);
if(!self._reject_writer.corked && !goOn) self._reject_writer.cork();
}
self.emit('reject', data);
cb();
}
});
this._reject_writer.on('prefinish', ()=>{
if(this._reject_reader) this._reject_reader.push(null);
});
this._error_reader.pipe(this._reject_writer);
this._cb = cb;
this.on('pipe', src => {
this._src = src;
})
}
/**
* Pipe to a destination stream when goal was achieved
* @param {Writable} dst
*/
resolve(dst) {
if(!this._resolve_reader) {
const self = this;
this._resolve_reader = new Readable({
objectMode: true,
read() {
if(self._resolve_writer) self._resolve_writer.uncork()
}
});
this._resolve_reader.options = {...this._resolve_reader.options||{}, name: this.options.name, port: 'resolve'};
this._resolve_reader._readableState.sync = false;
}
const pipes = Array.isArray(dst)?dst:[dst];
pipes.forEach(dst=>this._resolve_reader.pipe(dst));
return Array.isArray(dst)?this:dst;
}
/**
* Register a node style callback, called when goal is resolved or rejected
* @param {NodeCallback} cb
*/
callback(cb) {
this._cb = cb;
return this;
}
_registerChild(src) {
src.on('error', (error)=>{
this._error_reader.push(error);
})
}
/**
* Pipe to a destination stream when goal was not achieved
* @param {Writable} dst
*/
reject(dst) {
if(!this._reject_reader) {
this._reject_reader = new Readable({
objectMode: true,
read() {
if(self._reject_writer) self._reject_writer.uncork()
}
});
this._reject_reader.options = {...this._reject_reader.options||{}, name: this.options.name, port: 'reject'};
this._reject_reader._readableState.sync = false;
}
const pipes = Array.isArray(dst)?dst:[dst];
pipes.forEach(dst=>this._reject_reader.pipe(dst));
return Array.isArray(dst)?this:dst;
}
/**
* Thenable function to work with promises.
* @param {ThenCallback} thenCallback called when goal is achieved
* @param {CatchCallback} [catchCallback] called when goal is not achieved
* @returns {Promise}
*/
then(rescb, rejcb) {
return new Promise((resolve, reject) => {
this.on('resolve', data => {
if(!rescb) return resolve(data);
try {
resolve(rescb(data));
} catch(e) {
reject(e);
}
});
this.on('reject', data => {
if(!rejcb) return reject(data);
try {
resolve(rejcb(data));
} catch(e) {
reject(e);
}
})
});
}
/**
* Thenable function to work with promises.
* @param {CatchCallback} catchCallback called when goal is not achieved
* @returns {Promise}
*/
catch(rejcb) {
return this.then(null, rejcb);
}
build(chain, options) {
Manager._build(chain, {...options, goal: this});
}
_write(payload, encoding, cb) {
var data = new DataWrapper(payload, this);
//process.nextTick(()=>this._start.push(data));
let goOn = this._start.push(data);
if(!this._writableState.corked && !goOn) this.cork();
cb();
}
}
module.exports.Goal = Goal;
// module.exports.GoalGroup = GoalGroup;