2016-11-06 56 views
2

我已經使用RxJS成功將更大的Excel計算遷移到JS。任何輸入數據都表示爲Observable,隨後任何Excel公式使用多個輸入時,將使用.map.combineLatest執行後續計算。計算鏈中的其他運算符比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運算符)時仍然更新計算。我明白,這個例子顯然可以簡化爲省略中間步驟,並直接從$a,$b$c計算$abbc - 但在真實的示例中,這是不可行的。

下面是JSbin運行例如:https://jsbin.com/pipiyodixa/edit?js,console

+1

'abbc $ = ab $ .withLatestFrom(bc $,(ab,bc)=> ab + bc)'會影響所需的行爲,但是... ...我沒有充分理解語義這種情況,所以這是一個評論。 – cartant

+0

@cartant謝謝。不幸的是,這將無法正常工作,因爲c $的更新將被忽略,直到$或b $再次發出。 :-( –

+0

是的,我現在看到了,看到解決方案會很有趣,我可以想到涉及調度程序的一個相當不愉快的攻擊,但是......必須有一個更好的方法。 – cartant

回答

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這樣工作。我還沒有在更復雜的例子中測試它,所以這可能不是你最終可以使用的方式。

注意cache()運營商在RC.1刪除,有沒有替代現在:https://github.com/ReactiveX/rxjs/blob/master/CHANGELOG.md

+0

謝謝 - 這解決了這個問題,並給了我正確的開始。我現在創建了一個運算符,並用這個新運算符替換了我的'combineLatest'調用 - 看到我的答案在下面! –

1

基於@馬丁的(正確)的答案,我創建了一個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)); 
} 

這樣,我只需要.combineLatestDelayed代替原來的.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); 

Full JS Bin here