Javascript 计算链中除CombineTest之外的其他运算符,以避免冗余计算
我已经使用RxJS成功地将一个更大的Excel计算迁移到JS。任何输入数据都表示为可观察数据,当任何Excel公式使用多个输入时,后续计算将使用Javascript 计算链中除CombineTest之外的其他运算符,以避免冗余计算,javascript,rxjs,rxjs5,ngrx,Javascript,Rxjs,Rxjs5,Ngrx,我已经使用RxJS成功地将一个更大的Excel计算迁移到JS。任何输入数据都表示为可观察数据,当任何Excel公式使用多个输入时,后续计算将使用.map和.combinelateest实现 除了一个问题外,这很有效。以下是一个简化的示例: 三个输入(a$=1,b$=2,c$=3)用于两种不同的计算($ab=$a+$b=3第一种,$bc=$b+$c=5第二种),作为计算最终结果的中间步骤$abbc=$ab+$bc=8 当$b现在更新/发出新的输入值(例如,4)时,$abbc计算两次-第一次是在$
.map
和.combinelateest
实现
除了一个问题外,这很有效。以下是一个简化的示例:
三个输入(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
计算$abbc
——但在真实的实例中,这是不可能/不可行的
下面是JSbin中的运行示例:这里的问题是RxJS的行为在其设计中是正确的 它确实应该首先更新
b
=>ab
=>abbc
,然后更新bc
=>abbc
。它处理流中的值
您需要处理“层”中的值。a
,b
,c
,然后ac
,bc
,然后计算最终值abbc
我能想到的唯一方法是利用JavaScript执行上下文和setTimeout(()=>{},0)
的技巧。这样,您就不需要安排任何超时(实际上是真正的超时),只需在JavaScript完成当前执行后在另一个执行上下文中运行闭包
糟糕的是,为了避免多次重新发出值,您需要缓存更多的值(因为merge()
):
接线员是这里最重要的。当它第一次被ab$
或bc$
触发时,它会触发withLatestFrom()
来更新它的值,直到这个闭包执行结束(这是setTimeout()
技巧)
见现场演示:
另外,如果您添加a$,则下一步(5)代码>最终计算只执行一次(可能是好的也可能是坏的:))
我不知道这是否解决了您的问题,因为正如我所说的RxJS是这样工作的。我还没有在任何更复杂的例子上测试过它,所以也许这不是你最后可以使用的方法
请注意,cache()
操作符已在RC.1中删除,目前没有替换:根据@martin(正确)的回答,我创建了一个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));
}
这样,我只需要将原来的.combineLatest
调用替换为.combineLatest
:
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 Binabbc$=ab$。使用LatestFrom(bc$,(ab,bc)=>ab+bc)
会影响所需的行为,但。。。在这种情况下,我并不完全理解语义,所以这是一个评论。@cartant谢谢。不幸的是,这不起作用,因为c$的更新将被忽略,直到a$或b$再次发出:-(是的。我现在看到了。看到解决方案会很有趣。我可以想到一个涉及调度程序的相当令人不快的黑客攻击,但是…必须有更好的方法。@cartant我希望如此-从几周以来我一直在为这个问题挠头,但根本看不到任何解决方案谢谢-这解决了问题,给了我一个正确的开始。我现在创建了一个接线员,并用这个新接线员替换了我的CombineTest
呼叫-请参阅下面我的答案!
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);