RxJS 系列 – Transformation Operators

前言

前幾篇介紹過了 

Creation Operators

Filter Operators

Join Creation Operators

Error Handling Operators

這篇繼續介紹 Transformation Operators.

 

參考

Docs – Transformation Operators

 

map

就是 Array 的 map 咯

const obs = from([1, 2, 3, 4]);
obs.pipe(map(v => v + 10)).subscribe(v => console.log(v)); // 11..12..13..14

 

scan

scan 相等於 Array 的 reduce

const obs = from([1, 2, 3, 4]);
obs
  .pipe(
    scan((acc, value, _index) => {
      return acc + value;
    }, 0)
  )
  .subscribe(v => console.log(v)); // 1..3..6..10

每一次 acc 代表上一次 return value

value 表示 obs 這一次的值

而第一次發佈的時候, 由於沒有上一次, acc 的值將是 init value (也就是 scan 的第二個參數)

 

pairwise

pairwise 是 "一對" 的概念, 它每次接收都是 2 個值, 當前值和上一次值

const obs = from([1, 2, 3, 4]);
obs.pipe(pairwise()).subscribe(v => console.log(v)); // [1,2]..[2,3]..[3,4]

由於第一次發佈時, 沒有 "上一次值", 所以不會接收, 上面例子中, 發佈了 4 次, 但是接收只有 3 次. 

 

concatMap

之前介紹過 concat, 把一堆 Observables 丟給它, 它會從第一個開始 subscribe 直到那一個 Observable complete 後再去 subscribe 下一個 Observable, 直到那一堆 Observable 全部結束.

concat(o1, o2, o3, o4).subscribe()

concatMap 和 concat 概念一樣都是 complete 後去 subscribe 下一個. 不同地方在於那一堆 Observables 提供的方式

const obs = from([1, 2, 3, 4]);
obs.pipe(concatMap(v => of(v))).subscribe(v => console.log(v));

concatMap() 接收 obs 的值, 然後返回 Observable. 上面例子中 obs 發佈 4 次. concatMap 也就發佈 4 個 Observables

而這 4 個 Observables 就被 concat(o1, o2, o3, o4) 了.

所以你也能把它理解爲一種動態的 concat. 因爲 concat(observables) 是初始化就決定了多少個 observables 被放進去. 

而 concatMap 則是一個一個添加進去的.

 

mergeMap

理解了 concatMap 就理解了 mergeMap. 它就是動態的 merge.

merge 的特點是, 它不像 concat 那樣會等待 complete. 它會直接 subscribe 所以的 Observables.

 

switchMap

沒有 switch 只有 switchMap. 

switchMap 的接口和 concapMap, mergeMap 一樣, 接收 obs 的值, 並且返回一個 Observable

concatMap 和 mergeMap 會把返回的 Observable 堆疊起來 (concat 挨個挨個 subscribe, merge 直接 subscribe all)

但 switchMap 不會把 Observable 堆疊起來, 它會 subscribe Observable, 一旦有下一個 Observable, 它會 unsubscribe 上一個 Observable (丟棄它), 然後 subscribe 下一個 Observable

這就是它和 concapMap, mergeMap 最大的不同.

const obs = fromEvent(document, 'click');
obs
  .pipe(switchMap(v => fetch('https://random-data-api.com/api/v2/users').then(r => r.json())))
  .subscribe(v => console.log(v));

每次點擊就會發 ajax, 如果點擊很快, ajax 還沒有返回, 那麼會放棄上一次的請求, 馬上在發新的 ajax.

 

exhaustMap

switchMap  和 exhaustMap 的關係有點類似 debounceTime 和 throttleTime 的關係.

debounceTime 的特色是 delay and keep postpone

throttleTime 的特色是 immediately + skip

switchMap 有新 Observable 它就會丟棄舊的, subscribe 新的. 這樣連續就會導致 subscriber 一直接收不到值. 這個就像 debounceTime 的 keep postpone.

exhaustMap 則像 throttleTime, 有 Observable 後它就 subscribe. 在 Observable 沒有 complete 前, 它無視接下來每一個新的 Observables.

 

switchScan

switchScan 和 swtichMap 概念差不多, 只是引入了 scan 的概念.

const obs = fromEvent(document, 'click');
obs
  .pipe(
    switchScan((acc, _value, _index) => {
      return timer(2000).pipe(map(() => acc + 1));
    }, 0)
  )
  .subscribe();

第 1 秒 click, acc = 0 (初始值)

第 2 秒 click, acc = 0 因爲返回的 Observable 需要 2 秒, 而 click 太快了, swtich 的概念就是放棄之前的, 擁抱新的.

第 4 秒 click, acc = 1

第 6 秒 click, acc = 2

 

concatMap, switchMap, mergeMap, exhaustMap 小結

參考: RxJS 轉換類型 Operators (2) - switchMap / concatMap / mergeMap / exhaustMap

 

bufferTime

bufferTime 的作用是把 Observable 發佈的值緩存起來, 等時機到了一次接收

const obs = timer(0, 1000);
obs.pipe(bufferTime(3000)).subscribe(values => console.log(values));

效果

obs 每秒發佈一個值, 經過 bufferTime 會被緩存起來, 直到每 3 秒 bufferTime 發佈, console 纔會接收到 3 秒內緩存的所以值.

效果圖中, 第 3 次接收 4 個 values 是因爲計時有微差.

 

bufferCount

和 bufferTime 一樣都是先緩存, 然後在一起發佈. 區別是它不是以時間來計算, 它以數量做計算.

const obs = timer(0, 1000);
obs.pipe(bufferCount(3)).subscribe(values => console.log(values));

效果

緩存滿 3 個就一起發佈.

startBufferEvery

bufferCount 還有第二個參數叫 startBufferEvery, 雖然冷門, 但也可以瞭解一下.

它的玩法是這樣的

const obs = interval(1000);
obs
  .pipe(bufferCount(3, 2))
  .subscribe(values => console.log((performance.now() / 1000).toFixed(0) + 's', values));

bufferCount(3, 2) 表示每當 obs 發佈 2 次, subscrube 就接收一次包含 3 個值

效果

2 個點要注意.

一, 2, 4 值重複了, 因爲要去每一次 3 個 count, 但是每 2 次就收集一輪, 那就差了一個, 於是就拿之前的補上.

二, 第一次發佈等待了 3 秒, 第二次則是 2 秒, 因爲每一次要求 3 個 count, 而第一次 2 秒鐘收集時並不滿足要求, 而一到 3 秒中滿足了需求就立刻發佈.

 

buffer

buffer 的區別是, 我們可以完全控制什麼時機發布. 不只是 by time or by count, 可以 by whatever

const obs = timer(0, 1000);
obs.pipe(buffer(fromEvent(document, 'click'))).subscribe(values => console.log(values));

當 document click 的時候把緩存一併發佈.

 

bufferWhen

參考: Stack Overflow – What's the difference between the RxJS operators "buffer" and "bufferWhen"?

bufferWhen 和 buffer 有一個小區別. buffer 的參數是一個 Observable

bufferWhen 的參數是一個 () => Observable

obs
  .pipe(
    bufferWhen(
      () =>
        new Observable(subscriber => {
          console.log('Observable Init');
          document.addEventListener('click', () => {
            subscriber.next();
          });
          return () => {
            console.log('Observable Displose');
          };
        })
    )
  )
  .subscribe(values => console.log(values));

buffer 參數 Observable 只會被 subscribe 一次.

bufferWhen 參數返回的 Observable 在每一次原 obs 一併發佈後都會被 resubscribe

上面代碼的效果是這樣的 

 

bufferToggle

上面的 buffer operators 都只有一個 "發佈時機". 

bufferToggle 有 2 個時機, 一個是 "發佈時機", 另一個是其它 buffer operators 沒有的 "開始緩存時機".

 參數 1 是開始緩存, 參數 2 是發佈.

const obs = timer(0, 1000);
obs
  .pipe(bufferToggle(fromEvent(document, 'click'), () => fromEvent(document, 'contextmenu')))
  .subscribe(values => console.log(values));

效果

我在第 3 秒的時候 click 了一下, 第 6 秒 right click 了一下, 所以得到了 [3, 4, 5]

又在 第 10 秒 click 了一下, 第 13 秒 right click 了一下, 得到了 [10, 11, 12]

這就是所謂的, 控制開始和發佈時機. 不在時機內的值將會丟失. 像 0, 1, 2, 6, 7, 8, 9 都接收不到.

 

buffer operators 小結

參考: 30 天精通 RxJS (12): Observable Operator - scan, buffer

常用到的是 bufferTime, bufferCount, buffer.

bufferWhen 和 bufferToggle 我目前都沒有用過.

 

windowTime, windowCount, window, windowToggle, windowWhen

參考:

RxJS window() Transformation Operator

Stack Overflow – What does the `window` mean in RxJS?

30 天精通 RxJS(20): Observable Operators - window, windowToggle

window 和 buffer 基本上是一樣的. 唯一的區別是 buffer 接收的是 Array. window 接收的是 Observable (類似於 from(Array))

我們來看一個對比, 感受一下它們的區別

const obs = timer(0, 1000);
obs.pipe(bufferTime(10000)).subscribe(values => console.log(values));

10 秒鐘後會接收到 Array [0, 1, 2 ... 10]

換成 windowTime

const obs = timer(0, 1000);
obs
  .pipe(
    windowTime(10000),
    switchMap(v => v)
  )
  .subscribe(values => console.log(values));

1 秒鐘後就會接收到 value 1

一個 10 秒後才接收, 一個第 1 秒就開始接收了, 這就是所謂的 immediately

那什麼時候用 window 什麼時候用 buffer 呢? 我不清楚, 但我自己的經驗是絕大部分情況下用 buffer 就夠了.

 

groupBy

和 array 的 groupBy 一個概念

const obs = new Observable<{ name: string; age: number }>(subscriber => {
  subscriber.next({ name: 'dada', age: 1 });
  subscriber.next({ name: 'derrick', age: 2 });
  subscriber.next({ name: 'dada', age: 3 });
  subscriber.next({ name: 'derrick', age: 4 });
});
obs.pipe(groupBy(v => v.name)).subscribe(g => {
  g.subscribe(v => console.log(g.key, v));
});

當 obs 發佈新值的時候, groupBy 會依據 key 查看之前是否有創建過 GroupedObservable

如果沒有就創建新的, 並且發佈下去. 所以 subscribe 接收到的是 GroupedObservable 哦.

如果已經創建過了, 那麼它就 .next 把值傳下去.

上面的例子中, subscribe 會接收 2 次, 一個是 dada 的 GroupedObservable 另一個是 derrick 的 GroupedObservable

訂閱這些 Observable 就可以獲取到每次發佈的對象了.

我在項目中沒有用過 groupBy 一時也想不到什麼情況可能會用到它.

 

沒有介紹到的 Transformation Operators

expand

mergeScan

 

廢棄了的 Transformation Operators

mapTo

switchMapTo

concatMapTo

mergeMapTo

pluck

partition

exhaust

 

一句話總結

map : Array map

scan : Array reduce

pairwise : 每次接收 “一對”, [prev value, curr value]

concatMap : concat(動態 Observables)

mergeMap : merge(動態 Observables)

switchMap : 自動 subscribe map 返回的 Observable, 當有新的 Observable 自動 unsubscribe 上一個 Observable

exhaustMap : switchMap unsubscribe old + subscribe new, exhaustMap subscribe old + skip new (until old complete)

switchScan : switchMap 引入 scan 概念

bufferTime : 緩存值, 到時機一併發送 array

bufferCount : 緩存到量, 一併發送

buffer : 用 notification observable 控制一併發送時機

bufferToggle : 控制開始緩存時機和發送時機

bufferWhen : notification observable 會被 resubscribe, buffer 不會

window operators : 和 buffer 一樣, 唯一區別是它不接收 Array 而是 Observable 類似於 from(Array), 還有, 接收的時機不同, buffer 是等 array 滿了才接收, window 是一開始就持續接收直到 complete.

groupBy : 把值進行分組, 發佈 GroupedObservable

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章