2个依赖流安全更新(Safe update for 2 dependent streams)

2019-10-21 11:08发布

作为一个练习,我试图建立2个依赖流哪个更新彼此。

测试应用程序是一个简单的“英寸< - >厘米”转换器,具有两个输入编辑。

我遇到的问题是,我不能让我怎么能停止递归,导致一个场变化。

为了更好地解释这个问题,让我们来看看代码的相关部分:

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));
});

因此,我们定义了对象的每一个保持当前的相应值。

现在想象一下,我们以英寸为单位的值更改为2 (使用inValue.onNext(2);或通过键盘)。

接下来会发生什么 - 是处理程序#2被触发,它会调用厘米的值的相应重新计算。 这将导致以cmValue.onNext(0.7874015748031495)

这个呼叫实际上然后由处理程序#1处理,并引起英寸值(我们手动将一个)被重新计算,使用0.7874015748031495 * 2.54式这引起另一个inValue.onNext(1.99999999999999973)呼叫。

幸运的是 - 由于FP舍入误差这就是我们停下来。 但在其他情况下,这可能会导致更多的循环或甚至无限递归。

正如你所看到的-我部分地解决应用问题.distinctUntilChanged()这至少让我们免受任何变化无限递归,但我们可以看到-在这种情况下,它并没有解决完全因为是不相等的问题(由于FP操作性质)。

所以,问题是:如何将一个实现一个通用的双向绑定不会引起自递归呢?

我强调通用要记,使用.select()与舍入将是这一特定问题的部分解决方案,而不是通用的一个(我和其他人宁愿)。

完整的代码和演示: http://jsfiddle.net/ewr67eLr/

Answer 1:

在您的演示中,你有两个输入字段。 此输入“KEYUP”事件将是信息源,和输入值将是目的地。 在这种情况下,你不需要可变状态检查可观察的更新。

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; });

这里检查我的例子: http://jsfiddle.net/537Lrcot/2/



Answer 2:

我有一个类似的任务来解决我的项目。

首先,你必须选择自己的立场真相 - 那就是要代表你的测量值,假设你选择厘米。

现在你只有一个是从多个源获取更新流工作。

由于有保存不能完全按照输入它表示的值,你必须比整个浮显著个数字输出。 一个人是不可能衡量英寸的11显著位的精度,所以没有点显示转换值到该精度。

function myRound(x, digits) {
    var exp = Math.pow(10, digits);
    return Math.round(x * exp) / exp;
}

cmValue.subscribe(function(v) {
    if (document.activeElement !== cmElement) {
        cmElement.value = myRound(v, 3).toFixed(3);
    }
    if (document.activeElement !== inElement) {
        inElement.value = myRound(cmToIn(v), 3).toFixed(3);
    }
});

到目前为止,这里的工作示例: http://jsfiddle.net/ewr67eLr/4/

剩下的是一个边缘的情况下,当我们改变我们的工作重点,以自动计算的值,然后该值重新计算我们用不同的数字第一个值。

这可以通过创建已经由用户改变的值流来解决:

    cmInputValue = new Rx.BehaviorSubject(0),
    inInputValue = new Rx.BehaviorSubject(0),
        ...
Rx.Observable.fromEvent(cmElement, 'input').subscribe(function (e) {
    cmInputValue.onNext(e.target.value);
});
Rx.Observable.fromEvent(inElement, 'input').subscribe(function (e) {
    inInputValue.onNext(e.target.value);
});
cmInputValue.distinctUntilChanged().subscribe(function (v) {
    cmValue.onNext(v);
});
inInputValue.distinctUntilChanged().subscribe(function (v) {
    cmValue.onNext(inToCm(v));
});

http://jsfiddle.net/ewr67eLr/6/

现在,这是我能解决这个任务的最佳方式。



Answer 3:

下面就来解决这个问题(即不依赖于DOM的具体状态)纯算法的方式。 基本上只使用一个变量来检测递归和中止更新。

/* two way binding */
var twowayBind = function (a, b, aToB, bToA) {
    var updatingA = 0,
        updatingB = 0,
        subscribeA = new Rx.SingleAssignmentDisposable(),
        subscribeB = new Rx.SingleAssignmentDisposable(),
        subscriptions = new Rx.CompositeDisposable(subscribeA, subscribeB);

    subscribeA.setDisposable(a.subscribe(function (value) {
        if (!updatingB) {
            ++updatingA;
            b.onNext(aToB(value));
            --updatingA;
        }
    }));

    subscribeB.setDisposable(b.subscribe(function (value) {
        if (!updatingA) {
            ++updatingB;
            a.onNext(bToA(value));
            --updatingB;
        }
    });

    return subscriptions;
};

var cmValue = new BehavoirSubject(0),
    inValue = new BehaviorSubject(0),
    binding = twowayBind(cmValue, inValue, cmToIn, inToCm);


Answer 4:

正如我的评论指出,这个问题并不需要一个循环。 它还不需要主题,或document.activeElement。 你可以不称呼对方流从输入事件更新B,和事件从输入B更新。

这里的例子:

http://jsfiddle.net/kcv15h6p/1/

相关位的位置:

var cmElement = document.getElementById('cm'),
    inElement = document.getElementById('in'),
    cmInputValue = Rx.Observable.fromEvent(cmElement, 'input').map(evToValue).startWith(0),
    inInputValue = Rx.Observable.fromEvent(inElement, 'input').map(evToValue).startWith(0);


inInputValue.map(inToCm).subscribe(function (v) {
    cmElement.value = myRound(v, 3).toFixed(3);
});

cmInputValue.map(cmToIn).subscribe(function (v) {
    inElement.value = myRound(v, 3).toFixed(3);
});

对于那些真正需要循环的问题,您可以延迟创建循环,在布兰登的回答这个问题指出:

捉可观测量之间循环依赖

像任何循环结构,你必须处理的退出条件,以避免无限循环。 你可以像取(),或distinctUntilChanged运营商这样做()。 需要注意的是后者需要一个比较器,这样就可以,例如,使用对象标识(X,Y)=> X === Y,退出循环。



文章来源: Safe update for 2 dependent streams