Javascript 计算链中除CombineTest之外的其他运算符,以避免冗余计算

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计算两次-第一次是在$

我已经使用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计算两次-第一次是在$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 Bin

abbc$=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);