Using Bacon to build advanced EventEmitter type system… in particular how to dynamically change stream inputs?

119 views
Skip to first unread message

je...@hogbaysoftware.com

unread,
Sep 4, 2014, 4:52:31 PM9/4/14
to bac...@googlegroups.com
Hi,

I'm new to Bacon and FPR's in general, so please bear with me…

I'm trying to create an event emitter type system. In my system I'll have events (Bacon.Buses) and I'll have Models that listen to events AND send out there own events. The idea is that models will have multiple input streams and a single output stream. I want to use Bacon behind the scenes so that I can take combine event streams in different ways and most important so that I can have glitch free propagation through the dispatch.

At this point I can get everything working as I like if I hardcode connect all the streams together ahead of time. But I'm stuck when I try to figure out how to make models dynamically listen to and stop listening different sets events once the system is already setup.

Here's how I'd like my system to work, with comments in the places where I'm stuck:

//
// Events
//
var event1 = new Bacon.Bus();
var event2 = new Bacon.Bus();
var event3 = new Bacon.Bus();

//
// Model
//
function Model() {
this._nextEmit = undefined;
this.listenTo([event1], this.onEvent1);
this.listenTo([event2, event3], this.onEvent2OrEvent3);
}

Model.prototype.emit = function(e) {
this._nextEmit = e;
}

Model.prototype.listenTo = function(events, callback) {
var outerThis = this;

// Here I'm combining the events into a single stream.
// Then flatMapping that stream to values Model.prototype.emit(ed)
// by this model.
//
// In my first attempt at this I created a new Bacon.Bus for each model
// and made Model.prototype.emit just push emits into that stream.
// That implementation was easier for me to follow, but I lost glitch free
// propagation. So that's why I'm connecting the models output stream
// directly into the models input stream.
//
// Is there some better way to do this. I don't want clients of my event system
// to be away of Bacon. I just want them to be able to pass in a callback that will
// contain the input event values. And then if the model should emit it's own event
// they should just have to call Model.prototype.emit.
var emitStream = Bacon.combineAsArray(events)
.flatMap(function() {
callback.apply(undefined, arguments);
if (outerThis._nextEmit) {
var nextEmit = outerThis._nextEmit;
outerThis._nextEmit = undefined;
return nextEmit;
} else {
return Bacon.never();
}
});

// I need to do this, otherwise there's no Bacon observer in the system so no
// events ever get pulled though.
emitStream.onValue(function(each) {
});

// Other models that depend on this model can listenTo this emitStream.
// The big problem in my current implementation is that every listenTo
// overwrites this emitStream with a new one…
this.emitStream = emitStream;
}

Model.prototype.onEvent1 = function(e1) {
if (condition) {
this.emit('value');
}
}

Model.prototype.onEvent2OrEvent3 = function(e2, e3) {
if (condition) {
this.emit('value');
}
}

I hope I've explained the system well enough to explain it. I typed up this version in email (and made some simplifications) so there may be little errors… but I have something very similar that works well, except for the big problem that I can only call listenTo once.

I don't know how to make the models inputs dynamic while keeping the same instance of the models emitStream (and keeping glitch free propagation)… i.e. not introducing another Bacon.Bus for the models output.

Does anyone have tips or hints to get me going in the right direction?

Thanks,
Jesse

je...@hogbaysoftware.com

unread,
Sep 4, 2014, 7:37:37 PM9/4/14
to bac...@googlegroups.com, je...@hogbaysoftware.com
Here's a more concise restatement of the problem:

var event1 = new Bacon.Bus();
var event2 = new Bacon.Bus();
var event3 = new Bacon.Bus();

var combinedStream = Bacon.combineWith(function(e1, e2) {
return e1 + e2;
},
event1,
event2
);

combinedStream.onValue(function (each) {
console.log(each);
})

// Is there a way to modify the combinedStream so that it will include
// event3 events and no longer listent to event1 events? I want to
// leave the combinedStream onValue observer in place, but I want to
// change the streams that are being combined into it.
//
// I don't see any simple method on combinedStream to add/remove
// source streams. But I'm wondering if the basic behavior (maybe not
// even using combineWith at all) can be achived through some
// combination of custom streams, or flatMaps, or something! :)

Thanks,
Jesse

v.s...@gmail.com

unread,
Sep 21, 2014, 5:12:54 AM9/21/14
to bac...@googlegroups.com, je...@hogbaysoftware.com
flatMapLatest is all you need. Try this:

function selectStream(selector, streams) {
return selector.flatMapLatest(function(current_key) {
return streams[current_key];
})
}

Example:

var a = new Bacon.Bus();
var b = new Bacon.Bus();
var src = new Bacon.Bus();
var current = selectStream(src.toProperty('a'), {a: a, b: b});

current.assign(console, 'log')

console.log('a')
b.push('b foo');
a.push('a foo');
a.push('a bar');
b.push('b bar');
console.log('b')
src.push('b')
b.push('b foo');
a.push('a foo');
a.push('a bar');
b.push('b bar');

Reply all
Reply to author
Forward
0 new messages