计算链中的其他运算符比combine最大,以避免冗余计算

时间:2016-11-06 23:06:13

标签: javascript rxjs rxjs5 ngrx

我已经使用RxJS成功地将更大的Excel计算迁移到了JS。任何输入数据都表示为Observable,当任何Excel公式使用多个输入时,后续计算将使用.map.combineLatest实现。

除了一个问题外,这很有用。这是一个简化的例子: Excel screenshot

三个输入(a$=1b$=2c$=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

2 个答案:

答案 0 :(得分:2)

这里的问题是RxJS的行为是正确的。

首先应该更新b => ab => abbc然后bc => abbc。它处理流中的值。

您想要的是处理“图层”中的值。abc,然后是acbc,之后计算最终值价值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