2 个依赖流的安全更新
Safe update for 2 dependent streams
作为练习,我正在尝试构建 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/
我的项目也有类似的任务要解决。
首先,您必须选择您的基本事实 - 代表您的测量值的值,假设您选择厘米。
现在,您只使用一个从多个源获取更新的流。
由于必须存储一个在输入时无法精确表示的值,因此您必须使用比整个浮点数更少的有效数字输出它。一个人不太可能将英寸测量到 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/
现在这是我解决这项任务的最好方法。
在您的演示中,您有两个输入字段。此输入的"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/
这是一种纯粹的算法方法来解决问题(即它不依赖于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);
正如我的评论中所述,这个问题不需要循环。它也不需要 Subjects 或 document.activeElement。您可以让来自输入 A 更新 B 的事件和来自输入 B 更新 A 的事件,而无需相互引用的流。
这里的例子:
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);
});
对于真正需要循环的问题,你可以使用 defer 创建循环,正如 Brandon 对这个问题的回答所指出的那样:
捕获可观察量之间的循环依赖关系
与任何循环构造一样,您必须处理退出条件以避免无限循环。您可以使用 take(( 或 distinctUntilChanged(( 等运算符来执行此操作。请注意,后者采用比较器,因此例如,您可以使用对象标识 (x, y( => x === y 退出循环。
- 如何在Knockout.js中选中复选框时更新视图模型及其依赖项
- 具有多个依赖项的Ember属性不会按预期更新
- Knockout Js的自定义绑定处理程序更新依赖项
- 依赖于jquery和css的html标记中的安全问题
- 在 JavaScript 中依赖全局本机对象是否安全,尤其是 Object 对象
- 在 NodeJS 中使用和更新全局变量是否安全?
- 更新现有包的依赖项版本
- 如何在 npm 安装期间将分叉存储库更新为依赖项
- 离子选择不会更新依赖于作用域变量的功能
- 2 个依赖流的安全更新
- 通过 FirebaseRef.set 更新 Firebase 安全规则
- Knockout计算的observable在更改其依赖关系时不会更新值
- 如何更新Yeoman依赖项
- 推迟或暂停敲除中的依赖项评估,直到视图模型完全更新(例如使用映射插件)
- EmberJS-当相同的值被分配给依赖属性时,更新计算的属性
- node_module>——save不更新依赖项
- 更新chrome扩展:manifest 2和安全策略
- 更新依赖项后,npm安装无法工作
- 更新computed observable中依赖的observable
- RESTful API——处理依赖于其他更新的更新