30 天精通 RxJS (10): Observable Operator - combineLatest, withLatestFrom, zip

非同步最難的地方在於,當有多個非同步行爲同時觸發且相互依賴,這時候咱們要處理的邏輯跟狀態就會變得極其複雜,甚至程序極可能會在完成的一兩天後就成了 Legacy Code(遺留代碼)。javascript

昨天咱們最後講到了 merge 的用法,它的邏輯就像是 OR(||)同樣,能夠把多個 observable 合而且同時處理,當其中任合一個 observable 送出元素時,咱們都作相同的處理。java

今天咱們要講的三個 operators 則像是 AND(&&) 邏輯,它們都是在多個元素送進來時,只輸出一個新元素,但各自的行爲上仍有差別,須要讀者花點時間思考,建議在頭腦清醒時閱讀本篇文章。編輯器

Operators

combineLatest

首先咱們要介紹的是 combineLatest,它會取得各個 observable 最後送出的值,再輸出成一個值,咱們直接看示例會比較好解釋。ui

var source = Rx.Observable.interval(500).take(3);
var newest = Rx.Observable.interval(300).take(6);

var example = source.combineLatest(newest, (x, y) => x + y);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});
// 0
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// complete複製代碼

JSBin | JSFiddlespa

你們第一次看到這個 output 應該都會很困惑,咱們直接來看 Marble Diagram 吧!.net

source : ----0----1----2|
newest : --0--1--2--3--4--5|

    combineLatest(newest, (x, y) => x + y);

example: ----01--23-4--(56)--7|複製代碼

首先 combineLatest 能夠接收多個 observable,最後一個參數是 callback function,這個 callback function 接收的參數數量跟合併的 observable 數量相同,依照示例來講,由於咱們這裏合併了兩個 observable 因此後面的 callback function 就接收 x, y 兩個參數,x 會接收從 source 發送出來的值,y 會接收從 newest 發送出來的值。code

最後一個重點就是必定會等兩個 observable 都曾有送值出來纔會呼叫咱們傳入的 callback,因此這段程式是這樣運行的ip

  • newest 送出了 0,但此時 source 並無送出過任何值,因此不會執行 callback
  • source 送出了 0,此時 newest 最後一次送出的值爲 0,把這兩個數傳入 callback 獲得 0
  • newest 送出了 1,此時 source 最後一次送出的值爲 0,把這兩個數傳入 callback 獲得 1
  • newest 送出了 2,此時 source 最後一次送出的值爲 0,把這兩個數傳入 callback 獲得 2
  • source 送出了 1,此時 newest 最後一次送出的值爲 2,把這兩個數傳入 callback 獲得 3
  • newest 送出了 3,此時 source 最後一次送出的值爲 1,把這兩個數傳入 callback 獲得 4
  • source 送出了 2,此時 newest 最後一次送出的值爲 3,把這兩個數傳入 callback 獲得 5
  • source 結束,但 newest 還沒結束,因此 example 還不會結束。
  • newest 送出了 4,此時 source 最後一次送出的值爲 2,把這兩個數傳入 callback 獲得 6
  • newest 送出了 5,此時 source 最後一次送出的值爲 2,把這兩個數傳入 callback 獲得 7
  • newest 結束,由於 source 也結束了,因此 example 結束。

不論是 source 仍是 newest 送出值來,只要另外一方曾有送出過值(有最後的值),就會執行 callback 並送出新的值,這就是 combineLatest。內存

combineLatest 很經常使用在運算多個因子的結果,例如最多見的 BMI 計算,咱們身高變更時就拿上一次的體重計算新的 BMI,當體重變更時則拿上一次的身高計算 BMI,這就很適合用 combineLatest 來處理!get

zip

在講 withLatestFrom 以前,先讓咱們先來看一下 zip 是怎麼運行的,zip 會取每一個 observable 相同順位的元素並傳入 callback,也就是說每一個 observable 的第 n 個元素會一塊兒被傳入 callback,這裏咱們一樣直接用示例講解會比較清楚

var source = Rx.Observable.interval(500).take(3);
var newest = Rx.Observable.interval(300).take(6);

var example = source.zip(newest, (x, y) => x + y);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});
// 0
// 2
// 4
// complete複製代碼

JSBin | JSFiddle

Marble Diagram 長這樣

source : ----0----1----2|
newest : --0--1--2--3--4--5|
    zip(newest, (x, y) => x + y)
example: ----0----2----4|複製代碼

以咱們的示例來講,zip 會等到 source 跟 newest 都送出了第一個元素,再傳入 callback,下次則等到 source 跟 newest 都送出了第二個元素再一塊兒傳入 callback,因此運行的步驟以下:

  • newest 送出了第一個0,但此時 source 並無送出第一個值,因此不會執行 callback。
  • source 送出了第一個0,newest 以前送出的第一個值爲 0,把這兩個數傳入 callback 獲得 0
  • newest 送出了第二個1,但此時 source 並無送出第二個值,因此不會執行 callback。
  • newest 送出了第三個2,但此時 source 並無送出第三個值,因此不會執行 callback。
  • source 送出了第二個1,newest 以前送出的第二個值爲 1,把這兩個數傳入 callback 獲得 2
  • newest 送出了第四個3,但此時 source 並無送出第四個值,因此不會執行 callback。
  • source 送出了第三個2,newest 以前送出的第三個值爲 2,把這兩個數傳入 callback 獲得 4
  • source 結束 example 就直接結束,由於 source 跟 newest 不會再有對應順位的值

zip 會把各個 observable 相同順位送出的值傳入 callback,這很常拿來作 demo 使用,好比咱們想要間隔 100ms 送出 'h', 'e', 'l', 'l', 'o',就能夠這麼作

var source = Rx.Observable.from('hello');
var source2 = Rx.Observable.interval(100);

var example = source.zip(source2, (x, y) => x);複製代碼

這裏的 Marble Diagram 就很簡單

source : (hello)|
source2: -0-1-2-3-4-...
        zip(source2, (x, y) => x)
example: -h-e-l-l-o|複製代碼

這裏咱們利用 zip 來達到本來只能同步送出的資料變成了非同步的,很適合用在創建示範用的資料。

建議你們日常沒事不要亂用 zip,除非真的須要。由於 zip 必須 cache 住還沒處理的元素,當咱們兩個 observable 一個很快一個很慢時,就會 cache 很是多的元素,等待比較慢的那個 observable。這頗有可能形成內存相關的問題!

withLatestFrom

withLatestFrom 運行方式跟 combineLatest 有點像,只是他有主從的關係,只有在主要的 observable 送出新的值時,纔會執行 callback,附隨的 observable 只是在背景下運行。讓咱們看一個例子

var main = Rx.Observable.from('hello').zip(Rx.Observable.interval(500), (x, y) => x);
var some = Rx.Observable.from([0,1,0,0,0,1]).zip(Rx.Observable.interval(300), (x, y) => x);

var example = main.withLatestFrom(some, (x, y) => {
    return y === 1 ? x.toUpperCase() : x;
});

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製代碼

JSBin | JSFiddle

先看一下 Marble Diagram

main   : ----h----e----l----l----o|
some   : --0--1--0--0--0--1|

withLatestFrom(some, (x, y) =>  y === 1 ? x.toUpperCase() : x);

example: ----h----e----l----L----O|複製代碼

withLatestFrom 會在 main 送出值的時候執行 callback,但請注意若是 main 送出值時 some 以前沒有送出過任何值 callback 仍然不會執行!

這裏咱們在 main 送出值時,去判斷 some 最後一次送的值是否是 1 來決定是否要切換大小寫,執行步驟以下

  • main 送出了 h,此時 some 上一次送出的值爲 0,把這兩個參數傳入 callback 獲得 h
  • main 送出了 e,此時 some 上一次送出的值爲 0,把這兩個參數傳入 callback 獲得 e
  • main 送出了 l,此時 some 上一次送出的值爲 0,把這兩個參數傳入 callback 獲得 l
  • main 送出了 l,此時 some 上一次送出的值爲 1,把這兩個參數傳入 callback 獲得 L
  • main 送出了 o,此時 some 上一次送出的值爲 1,把這兩個參數傳入 callback 獲得 O

withLatestFrom 很經常使用在一些 checkbox 型的功能,例如說一個編輯器,咱們開啓粗體後,打出來的字就都要變粗體,粗體就像是 some observable,而咱們打字就是 main observable。

今日小結

今天介紹了三個合併用的 operators,這三個 operators 的 callback 都會依照合併的 observable 數量來傳入參數,若是咱們合併了三個 observable,callback 就會有三個參數,而無論合併幾個 observable 都會只會回傳一個值。

這幾個 operators 須要花比較多的時間思考,讀者們不用硬記他的運行行爲,只要稍微記得有這些 operators 能夠用就能夠了。等到真的要用時,再從新回來看他們的運行方式作選擇。

不知道讀者們今天有沒有收穫呢? 若是有任何問題,歡迎在下方留言給我,謝謝!

相關文章
相關標籤/搜索