我已经成功地使用RxJS将更大的Excel计算迁移到JS。任何输入数据都表示为可观察的,当任何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操作符)。我理解,这个示例显然可以简化为省略中间步骤,直接从$a
、$b
和$c
中计算出$b
,但在实际示例中,这是不可能的/不可行的。
下面是JSbin中运行的示例:https://jsbin.com/pipiyodixa/edit?js,console
发布于 2016-11-07 08:38:59
这里的问题是RxJS的行为通过它的设计是正确的。
它确实应该首先更新b
=> ab
=> abbc
,然后再更新bc
=> abbc
。它处理流中的值。
您想要的是处理“层”中的值。a
、b
、c
,然后是ac
、bc
,然后再计算最终值abbc
。
我能想到的唯一方法就是使用JavaScript执行上下文和使用setTimeout(() => {}, 0)
的技巧。这样,您就不会安排任何超时(实际上是真正的> 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是这样工作的。我还没有在任何更复杂的例子上测试过它,所以也许这不是您可以在最后使用的方法。
请注意,cache()
操作符在RC.1中已被删除,目前还没有替换:https://github.com/ReactiveX/rxjs/blob/master/CHANGELOG.md
发布于 2016-11-08 22:48:02
基于@martin的(正确)答案,我创建了一个执行combineLatestDelayed的rxjs操作符:
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));
}
这样,我只需要用.combineLatestDelayed
替换原始的.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);
完整JS Bin 这里
https://stackoverflow.com/questions/40455579
复制相似问题