我已经使用RxJS成功地将更大的Excel计算迁移到了JS。任何输入数据都表示为Observable,当任何Excel公式使用多个输入时,后续计算将使用.map
和.combineLatest
实现。
三个输入(a$=1
,b$=2
,c$=3
)用于两个不同的计算中(第一个$ab = $a+$b = 3
,第二个$bc = $b+$c = 5
)计算最终结果$abbc = $ab + $bc = 8
的中间步骤。
当$ b现在更新/发出新的输入值(例如4
)时,$ abbc计算两次 - 首先更新$ ab(导致错误的结果$abbc=10
)并再次当$ bc更新时,会得到正确的结果12
。
虽然最终结果是正确的,但中间计算既错误又冗余。有没有办法只在$b
得到更新的情况下执行最后一次计算 - 同时还在更新a$
或c$
时更新计算(这将排除zip运算符)。我了解这个示例显然可以简化为省略中间步骤并直接从$abbc
,$a
和$b
计算$c
- 但在实际的实例中这是不可能/不可行。
以下是JSbin中的运行示例:https://jsbin.com/pipiyodixa/edit?js,console
答案 0 :(得分:2)
这里的问题是RxJS的行为是正确的。
首先应该更新b
=> ab
=> abbc
然后bc
=> abbc
。它处理流中的值。
您想要的是处理“图层”中的值。a
,b
,c
,然后是ac
,bc
,之后计算最终值价值abbc
。
我能想到的唯一方法是利用JavaScript执行上下文和setTimeout(() => {}, 0)
的技巧。这样你就不会安排任何超时(实际上是真正的timeout might be > 0
),只是在JavaScript完成当前执行后,在另一个执行上下文中运行闭包。
糟糕的是,为避免多次重新发布值,您需要进行更多缓存(因为merge()
):
var b2$ = b$.cache(1);
var ab$ = a$
.combineLatest(b2$, (a, b) => a + b)
.do(x => console.log('$a + $b = ' + x))
.cache(1);
var bc$ = b2$
.combineLatest(c$, (b, c) => b + c)
.do(x => console.log('$b + $c = ' + x))
.cache(1);
var abbc$ = new Rx.Observable.merge(ab$, bc$)
.auditTime(0)
.withLatestFrom(ab$, bc$, (_, ab, bc) => ab + bc)
.do(x => console.log('$ab + $bc = ' + x));
console.log("Initial subscription:")
abbc$.subscribe();
b$.next(4);
auditTime()
运营商是最重要的事情。当withLatestFrom()
或ab$
第一次触发它时,它会触发bc$
更新它的值,它会忽略所有连续发出,直到此闭包执行结束(即setTimeout()
特技)。
查看现场演示:https://jsbin.com/zoferid/edit?js,console
另外,如果你添加a$.next(5);
,最后的计算只执行一次(可能好或坏:) :()
我不知道这是否能解决你的问题,因为我说RxJS是这样的。我没有在任何更复杂的例子上测试它,所以也许这不是你最后可以使用的方式。
请注意,在RC.1中删除了cache()
运算符,现在暂无替换:https://github.com/ReactiveX/rxjs/blob/master/CHANGELOG.md
答案 1 :(得分:1)
基于@ martin(正确)答案,我创建了一个rxjs运算符,它执行combineLatestDelayed:
Rx.Observable.prototype.combineLatestDelayed = function(b$, cb) {
var a2$ = this.cache(1);
var b2$ = b$.cache(1);
return a2$
.merge(b2$)
.auditTime(0)
.withLatestFrom(a2$, b2$, (_, a, b) => cb(a,b));
}
这样,我只需要用.combineLatest
替换原来的.combineLatestDelayed
来电:
var a$ = new Rx.BehaviorSubject(1).do(x => console.log("Emitting $a=" + x));
var b$ = new Rx.BehaviorSubject(2).do(x => console.log("Emitting $b=" + x)); var b1$ = b$.cache(1);
var c$ = new Rx.BehaviorSubject(3).do(x => console.log("Emitting $c=" + x));
var ab$ = a$
.combineLatestDelayed(b1$, (a, b) => a + b)
.do(x => console.log('ab$: $a + $b = ' + x))
var bc$ = b1$
.combineLatestDelayed(c$, (b, c) => b + c)
.do(x => console.log('bc$: $b + $c = ' + x))
var abbc$ = ab$
.combineLatestDelayed(bc$, (ab, bc) => ab + bc)
.do(x => console.log('$abbc: $ab + $bc = ' + x));
console.log("Initial subscription:")
abbc$.subscribe();
setTimeout(() => b$.next(4), 100);
Full JS Bin here