Javascript 2个相关流的安全更新

Javascript 2个相关流的安全更新,javascript,reactive-programming,rxjs,reactive-extensions-js,Javascript,Reactive Programming,Rxjs,Reactive Extensions Js,作为练习,我尝试构建两个相互更新的依赖流 测试应用程序只是一个英寸-厘米转换器,两个输入都是可编辑的 我遇到的问题是,我无法理解如何停止导致一个字段更改的递归 为了更好地解释这个问题,让我们看看代码的相关部分: var cmValue = new Rx.BehaviorSubject(0), inValue = new Rx.BehaviorSubject(0); # handler #1 cmValue.distinctUntilChanged().subscribe(functio

作为练习,我尝试构建两个相互更新的依赖流

测试应用程序只是一个英寸-厘米转换器,两个输入都是可编辑的

我遇到的问题是,我无法理解如何停止导致一个字段更改的递归

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

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));
});
因此,我们定义了每一个拥有当前对应值的主体

现在假设我们使用inValue.onNext2将英寸值更改为2;或者通过键盘

接下来发生的事情是,处理器2被触发,它调用相应的以厘米为单位的值的重新计算。其结果为cmValue.onNext0.7874015748031495

这个调用实际上由处理程序1处理,并使用0.7874015748031495*2.54公式重新计算我们手动输入的以英寸为单位的值,这将导致另一个inValue.onNext1.999999999973调用

幸运的是,由于FP舍入错误,我们到此为止。但在其他情况下,这可能会导致更多循环,甚至导致无限递归

正如你所看到的-我部分解决了应用.distinctUntilChanged的问题,它至少可以保护我们免受任何更改的无限递归,但正如我们所看到的-在这种情况下,它并不能完全解决问题,因为由于FP操作的性质,值是不相同的

所以问题是:如何实现一个根本不会导致自递归的通用双向绑定

我强调“通用”是为了说明,对于这个特定问题,使用.select和舍入将是部分解决方案,而不是我和其他所有人都喜欢的通用解决方案


完整的代码和演示:

在我的项目中,我有一个类似的任务要解决


首先,你必须选择你的基本事实——代表你的测量的值,比如说你选择厘米

现在,您只处理一个从多个源获取更新的流

由于必须在输入时存储一个不能精确表示的值,因此输出时必须使用比整个浮点数更少的有效数字。一个人不可能将英寸测量到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);
    }
});
到目前为止,这里有一个工作示例:

剩下的是一个边缘情况,我们将焦点更改为自动计算值,然后该值用不同的数字重新计算第一个值

这可以通过为用户更改的值创建流来解决:

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

现在,这是我解决这项任务的最好方法。

我在项目中有一个类似的任务要解决


首先,你必须选择你的基本事实——代表你的测量的值,比如说你选择厘米

现在,您只处理一个从多个源获取更新的流

由于必须在输入时存储一个不能精确表示的值,因此输出时必须使用比整个浮点数更少的有效数字。一个人不可能将英寸测量到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);
    }
});
到目前为止,这里有一个工作示例:

剩下的是一个边缘情况,我们将焦点更改为自动计算值,然后该值用不同的数字重新计算第一个值

这可以通过为用户更改的值创建流来解决:

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

现在这是我能解决这个任务的最好方法。

这里有一个纯粹的算法方法来解决这个问题,即它不依赖于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);

这里有一个纯粹的算法方法来解决这个问题,即它不依赖于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);

在演示中,您有两个输入字段。此输入的键控事件将是信息源,输入值将是目标。在这种情况下,您不需要可变状态来检查可观察对象的更新

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

检查我的示例:

在演示中,您有两个输入字段。此输入的键控事件将是信息源,输入值将是目标。在这种情况下,您不需要可变状态来检查可观察对象的更新

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

检查我的示例:

正如我在评论中所指出的,这个问题不需要循环。它也不需要主题或document.activeElement。您可以拥有来自输入A更新B的事件,以及来自输入B更新A的事件,而不需要流相互引用

示例如下:

此处的相关位:

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);
});
对于真正需要循环的问题,您可以使用defer创建循环,正如Brandon在回答这个问题时指出的:


与任何循环构造一样,必须处理退出条件以避免无限循环。您可以使用take或distinctUntilChanged等操作符来完成此操作。请注意,后者需要一个比较器,因此您可以使用对象标识x,y=>x==y退出循环。

如我在评论中所述,此问题不需要循环。它也不需要主题或document.activeElement。您可以拥有来自输入A更新B的事件,以及来自输入B更新A的事件,而不需要流相互引用

示例如下:

此处的相关位:

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);
});
对于真正需要循环的问题,您可以使用defer创建循环,正如Brandon在回答这个问题时指出的:


与任何循环构造一样,必须处理退出条件以避免无限循环。您可以使用take或distinctUntilChanged等操作符来完成此操作。请注意,后者需要一个比较器,因此,例如,您可以使用对象标识x,y=>x==y退出循环。

首先,您必须选择精确的基本事实,就像在初始实现中一样,然后我故意将其更改为没有比较器,以便答案是无偏的:-进一步阅读答案。。。我已经通读过了。是的,基本上和我想的相符。老实说,document.activeElement的把戏看起来很脏。我的意思是,我希望RxJS能够以更好的方式设计这样的应用程序。据我所知,你试图做的没有很好的数学表达,所以它要么丑陋要么复杂。我不明白为什么这里需要主题。此外,转换FN是反向的;这样不行吗?首先,你必须准确地选择你的基本事实,就像在最初的实现中那样,然后我故意将其更改为没有,以便答案是公正的:-进一步阅读答案。。。我已经通读过了。是的,基本上和我想的相符。老实说,document.activeElement的把戏看起来很脏。我的意思是,我希望RxJS能够以更好的方式设计这样的应用程序。据我所知,你试图做的没有很好的数学表达,所以它要么丑陋要么复杂。我不明白为什么这里需要主题。此外,转换FN是反向的;这样不行吗?是的,我的另一个原始解决方案是类似的:我想创建一个共享流,如果需要传播递归,它将携带一个带有值和标志的对象,我的另一个原始解决方案与此类似:我想创建一个共享流,如果需要传播递归,它将携带一个带有值和标志的对象。从问题中不清楚为什么需要递归。您可以拥有来自输入A更新B的事件,以及来自输入B更新A的事件,而不需要流相互引用。例如,分配给cmElement.value不会生成事件,因此不存在循环。例如,通过引入不需要解决所述问题的相关主题,您在这里创建了一个循环。但是,您可以在需要时在Rx中安全地创建循环。也许用需要循环的问题更新问题。@user1009908:我不需要递归。我的问题是如何消除它。也许用一个需要循环的问题更新这个问题。问题已经描述过了,对不起。我喜欢你的解决方案。想把它作为一个答案吗?这个问题不清楚为什么需要递归。您可以拥有来自输入A更新B的事件,以及来自输入B更新A的事件,而不需要流相互引用。例如,分配给cmElement.value不会生成事件,因此不存在循环。例如,通过引入不需要解决所述问题的相关主题,您在这里创建了一个循环。但是,您可以在需要时在Rx中安全地创建循环。也许用需要循环的问题更新问题。@user1009908:我不需要递归。我的问题是如何消除它。也许用一个需要循环的问题更新这个问题。问题已经描述过了,对不起。我喜欢你的解决方案。想把它作为答案吗?