Having different Bacon.js streams compute their values in the same intermittent window

126 Views Asked by At

Perhaps, having the Bacon.js hammer in hand, everything starts to look like a nail. But this feels like a problem that could be elegantly solved with it, and I don't have enough experience yet to figure it out.

Many places in my application need to have their own Bacon.EventStream. Computing the values of these various streams requires some expensive setup and breakdown:

var stream1 = Bacon.fromPoll(100, function () {
    setup();                 // expensive
    var result = compute1(); // cheap
    breakdown();             // expensive
    return new Bacon.Next(result);
});

// similar for stream2, stream3, etc.

But the setup and breakdown is the same for all such streams. Therefore, it makes sense to group as many of those computations together as possible:

Bacon.interval(100, function () {
    setup();
    // compute value for property1
    // compute value for property2
    // compute value for property3
    // ...
    breakdown();
});

But all those independent streams will be set up in different places in the code, and may have different polling rates and such. Each of them should wait for the next available window to compute their value. The pacing would be set by some global stream providing this window.

  1. A computation should never be performed outside of a window.
  2. As many computations as possible should be grouped within the same window.
  3. Preferably, setup and breakdown should not occur if there are no computations to perform.

I've been playing around, and the JSFiddle here is the best I've come up with so far. But (1) it isn't correct; .skipWhile(property) seems to only skip one time, and (2) the code is already looking too complex. Can you do better?


Edit: I updated the JSFiddle. It's working correctly now. But I wonder whether I'm using best practices. Can this code be simplified?

1

There are 1 best solutions below

2
On BEST ANSWER

I refactored your fiddle to provide the windowed calculation as a single function and added buffering, so incoming values are buffered until the next available window. See updated jsFiddle.

function calculationWindow(pacing) {
    var wantedBus = new Bacon.Bus();
    var open = new Bacon.Bus();
    var close = new Bacon.Bus();

    pacing.filter(wantedBus.toProperty(false))
          .onValue(function () {
              console.log('<window>');
              open.push();
              wantedBus.push(false);
              close.push();
              console.log('</window>');
          });

    return function (stream) {
        wantedBus.plug(stream.map(true))
        return close.startWith(true).flatMapLatest(function() {
            return stream.takeUntil(open).reduce([], function(arr, val) {
                var a = arr.slice(0);
                a.push(val);
                return a;
            }).flatMap(Bacon.fromArray)
        })
    }
}

The benefit of doing this in a centralised place is that you can create your window handling once and then use wrap any stream with the resulting function.

Edit: If you don't want to buffer the values, but instead only care about the latest value, you can simplify the returned function to:

return function (stream) {
    wantedBus.plug(stream.map(true))
    return close.startWith(true).flatMapLatest(function() {
        return open.map(stream.toProperty())
    })
}

http://jsfiddle.net/omahlama/omp0xr2c/2/