Search code examples
javascriptreactive-programmingrxjsreactive-extensions-js

Safe update for 2 dependent streams


As an exercise I'm trying to build 2 dependent streams which update one another.

The test application is simply an "Inches <-> Centimeters" converter, with both inputs editable.

The issue I am experiencing is that I cannot get how can I stop recursion that causes one field change.

To better explain the issue let's have a look at the relevant part of code:

var cmValue = new Rx.BehaviorSubject(0),
    inValue = new Rx.BehaviorSubject(0);

# handler #1
cmValue.distinctUntilChanged().subscribe(function(v) {
    inValue.onNext(cmToIn(v));
});

# handler #2
inValue.distinctUntilChanged().subscribe(function (v) {
    cmValue.onNext(inToCm(v));
});

So we define to Subjects each of which holds the current corresponding value.

Now imagine we change the value in inches to 2 (using inValue.onNext(2); or via keyboard).

What happens next - is the handler #2 is triggered and it invokes a corresponding recalculation of a value in centimeters. Which results to cmValue.onNext(0.7874015748031495).

This call in fact is then handled by handler #1 and causes the value in inches (the one we put manually) to be recalculated, using 0.7874015748031495 * 2.54 formula which causes another inValue.onNext(1.99999999999999973) call.

Luckily - due to FP rounding error that's where we stop. But in other scenarios this may lead to more loops or even to an infinite recursion.

As you can see - I partially solved the issue applying .distinctUntilChanged() which at least protects us from an infinite recursion on any change, but as we can see - in this case it's does not solve the problem entirely since values are not identical (due to FP operations nature).

So the question is: how would one implement a generic two-way binding that does not cause self-recursion at all?

I emphasized generic to make a note that using .select() with rounding would be a partial solution for this particular issue, and not the generic one (which I and everyone else would prefer).

The complete code and demo: http://jsfiddle.net/ewr67eLr/


Solution

  • In your demo you have two input fields. "Keyup" events for this inputs will be the information source, and inputs value will be destination. In this case you don't need mutable states for checking updates of observables.

    Rx.Observable.fromEvent(cmElement, 'keyup')
        .map(targetValue)
        .distinctUntilChanged()
        .map(cmToIn)
        .startWith(0)
        .subscribe(function(v){ inElement.value = v; });
    
    Rx.Observable.fromEvent(inElement, 'keyup')
        .map(targetValue)
        .distinctUntilChanged()
        .map(inToCm)
        .startWith(0)
        .subscribe(function(v){ cmElement.value = v; });
    

    Check my example here: http://jsfiddle.net/537Lrcot/2/