rxjs入門6之合併資料流

長安城下翩翩少年發表於2019-05-14

一 concat,merge,zip,combineLatest等合併類操作符

以上操作符在版本6中已經只存在靜態方法,不能在pipe中使用。

import {concat,merge,zip,combineLatest}
1.concat (observable1,obs2,obs3) 首尾相連

依次將多個observable首尾合併,必須在第一個observable的資料全部完成,才能進行第二個obs2,如果第一個為interval(1000),那麼obs2 和obs3 也就永遠沒有機會得到輸出。

concat(of(1,2,3),interval).subscribe(console.log);
// 1   2   3    0  1  2  3  ...
2.merge 先到先得快速通過

merge會第⼀時間訂閱所有的上游Observable,然後對上游的資料採取“先到先得”的策略,任何⼀個Observable只要有資料推下來,就⽴刻轉給下游Observable物件。

merge(interval(1000),of(1,2,3)).subscribe(console.log);
merge(of(1,2,3),interval(1000)).subscribe(console.log);
//兩種情況的輸出結果一樣,都是先一次性輸出1 2 3  再間隔一秒依次輸出0 1 2 ...
const source1$ = Observable.timer(0, 1000).map(x => x+'A');
const source2$ = Observable.timer(500, 1000).map(x => x+'B');
merge(source1$, source2$).subscribe(
console.log,
null,
() => console.log('complete')
);
//0A
//0B
//1A
//1B
//2A
//2B

rxjs入門6之合併資料流

merge 的應用場景:我們知道fromEvent可以從⽹頁中獲取事件,只可惜,fromEvent⼀次只能從⼀個DOM元素獲取⼀種型別的事件。⽐如,我們關⼼某個元素的click事件,同時也關⼼這個元素上的touchend事件,因為在移動裝置上touchend事件出現得⽐click更早,這兩個事件的處理是⼀模⼀樣的,但是fromEvent不能同時獲得兩個事件的資料流,這時候就要藉助merge的⼒量了,程式碼如下:

const click$ = Rx.Observable.fromEvent(element, 'click');
const touchend$ = Rx.Observable.fromEvent(element, 'touchend');
merge(click$, touchend$).subscribe(eventHandler)
3.zip :拉鍊式組合

一對一的合併

  • zip會把上游的資料轉化為陣列形式,每⼀個上游Observable貢獻的資料會在對應陣列中佔⼀席之地.
  • 預設的輸出格式為陣列格式,可通過第二個引數進行引數格式組裝
  • 簡而言之:不管是同步產生資料還是非同步產生的資料,都會每次依次從需要合併的observable中取一個資料合併成一個陣列輸出,當某一個observer不再吐出資料了,則終止合併,執行complete函式
const source1$ = Observable.of(1, 2, 3);
const source2$ = Observable.of('a', 'b', 'c');
zip(source1$, source2$).subscribe(
    console.log,
    null,
    () => console.log('complete')
);
//[ 1, 'a' ]
//[ 2, 'b' ]
//[ 3, 'c' ]
//complete

rxjs入門6之合併資料流

4.combineLatest:合併最後一個資料
  • 輸出的陣列中元素個數與合併的observable的個數相等。
  • 在合併的observable中,只有最後一個元素為下游,前面的引數如果有同步的資料,同步資料中只有最後一個資料能進入資料流
  • 預設的輸出格式為陣列格式,可通過第二個引數進行引數格式組裝
  • 第一次執行,當上遊產生了資料,下游還沒來得及產生資料時,就會等待。第二輪時候,不管是上游或者下游產生一個資料,都會執行輸出,還沒來得及產生資料的observable就輸出原來產生的資料,如下彈珠圖
const source1$ = Observable.timer(500, 1000);
const source2$ = Observable.timer(1000, 1000);
combineLatest(source2$).subscribe(
    console.log,
    null,
    () => console.log('complete')
);
//[ 0, 0 ]
//[ 1, 0 ]
//[ 1, 1 ]
//[ 2, 1 ]
//[ 2, 2 ]
//[ 3, 2 ]

rxjs入門6之合併資料流

5.withLatestFrom
  • withLatestFrom 為管道中使用的方法。預設的輸出格式為陣列格式,可通過第二個引數進行引數格式組裝
  • withLatestFrom只有例項操作符的形式,⽽且所有輸⼊Observable的地位並不相同,調⽤withLatestFrom的那個Observable物件起到主導資料產⽣節奏的作⽤,作為引數的Observable物件只能貢獻資料,不能控制產⽣資料的時機。
const source1$ = Observable.timer(0, 2000).map(x => 100 * x);
const source2$ = Observable.timer(500, 1000);
 source1$.pipe(
    withLatestFrom(source2$, (a,b)=> a+b);
).subscribe(
    console.log,
    null,
    () => console.log('complete')
);

rxjs入門6之合併資料流
source1$產⽣第⼀個資料0時,withLatestFrom的另⼀個輸⼊Observable物件source2$還沒有產⽣資料,所以這個0也被忽略了。

解決glitch
例1:

const original$ = Observable.timer(0, 1000);
const source1$ = original$.map(x => x+'a');
const source2$ = original$.map(x => x+'b');
const result$ = source1$.pipe(withLatestFrom(source2$);)
result$.subscribe(
console.log,
null,
() => console.log('complete')
);

例2:

const event$ = Rx.Observable.fromEvent(document.body, 'click');
const x$ = event$.map(e => e.x);
const y$ = event$.map(e => e.y);
const result$ = x$.pipe(combineLatest(y$, (x, y) => `x: ${x}, y: ${y}`)).subscribe(
    (location) => {
        console.log('#render', location);
        document.querySelector('#text').innerText = location;
    }
);
race :勝者通吃

race就是“競爭”,多個Observable物件在⼀起,看誰最先產⽣資料,不過這種競爭是⼗分殘酷的,勝者通吃,敗者則失去所有機會。
簡而言之,通過race合併多個observable時,最先吐出資料那個observable會成為資料來源,其它的observable會被淘汰。

startWith

startWith只有例項操作符的形式,其功能是讓⼀個Observable物件在被訂閱的時候,總是先吐出指定的若⼲個資料。下⾯是使⽤startWith的⽰例程式碼

of(0,1,2).pipe(startWith('a','b')).subscribe(console.log);
//先依次吐出 a b 0 1 2
forkJoin
  • forkJoin可以接受多個Observable物件作為引數,forkJoin產⽣的Observable物件也很有特點,它只會產⽣⼀個資料,因為它會等待所有引數Observable物件的最後⼀個資料,也就是說,只有當所有Observable物件都完結,確定不會有新的資料產⽣的時候,forkJoin就會把所有輸⼊Observable物件產⽣的最後⼀個資料合併成給下游唯⼀的資料。
  • forkJoin就是RxJS界的Promise.all,Promise.all等待所有輸⼊的Promise物件成功之後把結果合併,forkJoin等待所有輸⼊的Observable物件完結之後把最後⼀個資料合併。
  • 返回陣列形式,陣列中元素個數為合併的observable的個數
    js forkJoin(interval(1000).pipe(take(3)),of(1,2,3),timer(2000,1000).pipe(take(3))).subscribe(console.log); // [2,3,2]js

高階Observable

簡言之:⾼階函式就是產⽣函式的函式;類似,所謂⾼階Observable,指的是產⽣的資料依然是Observable的Observable

1.concatAll

concatAll只有⼀個上游Observable物件,這個Observable物件預期是⼀個⾼階Observable物件,concatAll會對其中的內部Observable物件做concat的操作.

interval(1000).pipe(
    take(2),
    map(x=>interval(1500).pipe(take(2),map(x=> `${x}:x,y:${y}`))),
    concatAll()
).subscribe(console.log);
// 0:a,b:0
// 0:a,b:1
// 1:a,b:0
// 1:a,b:1

rxjs入門6之合併資料流

concat 實際運用

fromEvent(document.body,'mousedown').pipe(
      map(
        e=>fromEvent(document.body,'mousemove').pipe(map(e=>{return {x:e.clientX,y:e.clientY}}), takeUntil(fromEvent(document.body,'mouseup')))
      ),
      concatAll()
    ).subscribe(console.log);
mergeAll

mergeAll就是處理⾼階Observable的merge,只是所有的輸⼊Observable來⾃於上游產⽣的內部Observable物件.

interval(1000).pipe(
    take(2),
    map(x => Observable.interval(1500).map(y => x+':'+y).take(2)),
    mergeAll()
)

rxjs入門6之合併資料流
mergeAll只要
發現上游產⽣⼀個內部Observable就會⽴刻訂閱,並從中抽取收據,所以在上圖中,第⼆個內部Observable產⽣的資料1:0會出現在第⼀個內部Observable產⽣的資料0:1之前.

zipAll
interval(1000).pipe(
    take(2),
    map(x => Observable.interval(1500).map(y => x+':'+y).take(2)),
    zipAll()
)
//[ '0:0', '1:0' ]
//[ '0:1', '1:1' ]
//complete
combeneAll

combineAll就是處理⾼階Observable的combineLatest,可能是因為combine-LatestAll太長了,所以RxJS選擇了combineAll這個名字。

interval(1000).pipe(
    take(2),
    map(x => Observable.interval(1500).map(y => x+':'+y).take(2)),
    combeneAll()
)
//[ '0:0', '1:0' ]
//[ '0:1', '1:0' ]
//[ '0:1', '1:1' ]
//complete
switch
  • switch的含義就是“切換”,總是切換到最新的內部Observable物件獲取資料。每當switch的上游⾼階Observable產⽣⼀個內部Observable物件,switch都會⽴刻訂閱最新的內部Observable物件上,如果已經訂閱了之前的內部Observable物件,就會退訂那個過時的內部Observable物件,這個“⽤上新的,捨棄舊的”動作,就是切換。
  • 應用場景:也就是外層的資料產生快於內層的資料產生的速度造成資料積壓,需求又能夠捨棄原來的舊的外層的資料不讓其舊的外層資料再傳遞到內層產生資料了。
    簡而言之,當外層新產生資料時,無論內部資料產生情況如何都作廢,重新計算資料流
interval(1000).pipe(
    take(3),
    map(x => Observable.interval(1500).map(y => x+':'+y).take(2)),
    switch()
)
//1:0
//1:1
//complete

rxjs入門6之合併資料流
第⼀個Observable物件有機會產⽣資料0:0,但是在第⼆個資料0:1產⽣之前,第⼆個內部Observable物件產⽣,這時發⽣切換,第⼀個內部Observable就退場了。同樣,第⼆個內部Observable只有機會產⽣⼀個資料1:0,然後第三個內部Observable物件產⽣,之後沒有新的內部Observable物件產⽣,所以第三個Observable物件的兩個資料2:0和2:1都進⼊了下游。

exhaust
  • 在耗盡當前內部Observable的資料之前不會切換到下⼀個內部Observable物件
  • 同樣是連線⾼階Observable產⽣的內部Observable物件,但是exhaust的策略和switch相反,當內部Observable物件在時間上發⽣重疊時,情景就是前⼀個內部Observable還沒有完結,⽽新的Observable又已經產⽣,到底應該選擇哪⼀個作為資料來源?switch選擇新產⽣的內部Observable物件,exhaust則選擇前⼀個內部Observable物件.
interval(1000).pipe(
    take(3),
    map(x => Observable.interval(700).map(y => x+':'+y).take(2)),
    exhaust()
)

rxjs入門6之合併資料流

相關文章