RxJS 系列 – Filter Operators

前言

介紹完 RxJS 兩大概念 ObservableSubject 之後, 篇幅最大的就是各種 Operators 了.

這篇先介紹比較簡單的 Filter Operators. 請先看完上面 2 篇哦. 因爲例子會用到一些之前介紹過的方法.

 

參考

Docs – Filtering Operators

超級多, 這裏我只介紹我自己有用過, 或者以後有可能會用到的. 順序會依照我最常用和相關性排.

 

filter

const source = from([1, 2, 3, 4]);
const odd$ = source.pipe(filter(v => v % 2 !== 0));
const even$ = source.pipe(filter(v => v % 2 === 0));
odd$.subscribe(v => console.log(v)); // 1..3 (only value 1 and 3 will call)
even$.subscribe(v => console.log(v)); // 2..4

之前提過, stream 和 Array 很像. 所以它也有 filter 的概念.

每當發佈一個 new value, 先經過 filter, 如果 ok, 最終 subscriber 才能接收到.

 

first

顧名思義, 就是第一個發佈的 value 才接收

const source = from([1, 2, 3, 4]);
source.pipe(first(v => v % 2 !== 0)).subscribe(v => console.log(v)); // 1

此外, filter 可以傳入 filter 參數哦. 上面這個表示, 第一個 odd value 才接收.

 

last

有 first 自然就有 last. 不過 last 比較可愛的地方是, 怎麼知道是最後一個呢? 

所以它需要一個 complete 才能確保哪一個是最後一個.

const obs = new Observable<number>(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  subscriber.next(4);
  subscriber.complete(); // 重要
});
obs.pipe(last(v => v % 2 !== 0)).subscribe(v => console.log(v)); // 3

如果少了 complete, subscriber 將永遠不會接收到 last value.

 

elementAt

指定拿第幾個發佈值.

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

如果 Observable 結束都沒有達到指定的 index, 它會報錯, 可以通過設置 default value 解決這個報錯.

const obs = from([1, 2, 3, 4]);
obs.pipe(elementAt(100, 'default value')).subscribe(v => console.log(v)); // 'default value'

 

skip

顧名思義, 就是跳過幾個

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

 skip(2) 表示前面 2 次發佈不接收.

 

skipLast

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

和 last 一樣, Observable 一定要有 complete 哦, 不然不知道哪個算 last.

 

skipUntil

一直 skip 直到參數 Observable 發佈

const obs = interval(1000); // 0..1..2..3..4..5
obs.pipe(skipUntil(timer(4000))).subscribe(v => console.log(v)); // 3..4..5

obs 每一秒發佈一下. skipUntil 的參數 Observable 4 秒後發佈. 於是前面 4 秒的值都被 skip 掉了.

爲什麼 3 沒有被 filter, 那時因爲 interval 是慢一拍的. 之前文章有解釋過了.

如果覺得亂可以改成

const obs = timer(0, 1000); // 0..1..2..3..4..5
obs.pipe(skipUntil(timer(4000))).subscribe(v => console.log(v)); // 4..5

 

skipWhile

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

skip 和 skipUntil 都沒有辦法依據 value 來判斷是否持續 skip. 而 skipWhilte 補上了這個缺失.

上面的例子表示, 只要 value <=2 就一直 skip 下去, 直到 value > 2 出現, 纔開始接收.

 

take

take 和 skip 是相反的概念

skip(2) 表示頭 2 個 "不要", 剩餘的都 "要"

take(2) 表示頭 2 個 "要", 剩餘的 "不要"

const source = from([1, 2, 3, 4]);
source.pipe(take(2)).subscribe({
  next: v => console.log(v), // 1..2
  complete: () => console.log('complete'), // complete when dispatch 2
}); 

另外, 由於剩餘的都不要了, 所以它會提前結束 stream displose Observable.

 

takeLast

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

只拿最後 2 個. 但凡有 "last" 的 operators, Observable 記得要 complete 哦, 而且 complete 了以後 subscriber 纔會接收到 value.

 

takeUntil

const obs = timer(0, 1000); // 0..1..2..3..4..5
obs.pipe(takeUntil(timer(3000))).subscribe(v => console.log(v)); // 0..1..2

一直拿到 3 秒, 往後的都不要了

 

takeWhile

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

依據 value 判斷要拿到什麼時候停.

 

distinct

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

但凡出現過的 value 就不再第二次接收.

key selector

value 的 compare 方式是 ===

這種 compare 最怕就是遇到對象了. 因爲有時候對象指針雖然不同, 但其實是同一個 value 來的.

const source = from([
  { id: 1, name: 'Derrick' },
  { id: 2, name: 'Xinyao' },
  { id: 1, name: 'Derrick' },
]);
source.pipe(distinct(v => v.id)).subscribe(v => console.log(v)); // 1..2

這時就可以提供一個 key selector 函數. 這樣對比的方式從 v === v 變成了 v.id === v.id

 

distinctUntilChanged

distinct 是指, 但凡過往只要出現過, 那麼就不接收.

distinctUntilChanged 則是, 如果上一個 value 和當前這一個一樣的話, 那就不接收. 重點在它只 care 上一個而且, distinct 是看過往所有的 value.

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

distinctUntilChanged 只 filter 了中間區域的兩個 3. 因爲中間連續了三個 3.

key selector and comparator

distinctUntilChanged 比 distinct 更 cutomize, 它除了可以提供 key selector, 甚至連 compare 的方法想從 === 換成 == 都可以

 

distinctUntilKeyChanged

source.pipe(distinctUntilKeyChanged('id')).subscribe(v => console.log(v));

它底層只是調用了 distinctUntilChanged 而已. 沒有必要學這個, 用 distinctUntilChanged 就可以了.

 

debounceTime

debounceTime 常用於限制過於頻密的發佈, 比如 user typing ajax search

當用戶連續打字的時候, 我們不會發送 ajax search, 直到用戶停止輸入後才發 ajax.

const subject = new Subject<string>();
subject.pipe(debounceTime(2000)).subscribe(v => console.log(v));
subject.next('a');
await delayAsync(1000);
subject.next('b'); // 還不到 2 秒又發佈了, a 被取消接收
await delayAsync(1000);
subject.next('c'); // 還不到 2 秒又發佈了, b 被取消接收
// 2 秒後不再有發佈, c 被接收

debounceTime(2000) 表示, Subject 發佈後, 會先觀察 2 秒, 如果沒有下一個發佈, 那麼這個值將被接收, 如果 2 秒內又有新的發佈, 那就就丟棄上一個值, 繼續觀察下一個 2 秒.

所以, 如果每一次發佈間隔都小於 2 秒, 那麼觀察會一直進行下去, 不會有任何接收.

 

debounce

參考: RxJS 學習系列 10. 過濾操作符 debounce,debounceTime,throttle,throttleTime

debounce 比較少用到, 它和 debounceTime 類似, 都是用於限制頻密發佈.

但它比較厲害的地方是可以控制 duration. debounceTime 只能 set 死一個秒. debounce 可以動態控制

const subject = new Subject<string>();
const durationSelector = (value: string) => timer(2000);
subject.pipe(debounce(durationSelector)).subscribe(v => console.log(v));

每當 Subject 發佈, durationSelector 就會被調用, 它會拿到 Subject 發佈的值, 然後返回一個 duration observable

這個 duration observable 就是一個觀察期. 在 duration observable 發佈以前, 如果 subject 又發佈, 那麼上一次 subject 發佈的值就被丟棄. 然後 durationSelector 再次被調用. 等待下一個觀察期.

 

throttleTime

throttleTime 和 debounceTime 類似, 都是限制頻密發佈的.

區別在於, 當 Subject 發佈後, 會立馬接收, 然後進入關閉期. 在關閉期間, 如果 Subject 又發佈, 那麼就拒絕接收 (filter 掉)

const subject = new Subject<string>();
subject.pipe(throttleTime(1000)).subscribe(v => console.log(v));
subject.next('v'); // 接收
subject.next('v'); // 拒絕 (因爲在 1 秒內又發佈了)
await delayAsync(1000);
subject.next('v2'); // 接收 (因爲已經過了 1 秒的關閉期)

 

throttle

throttle & throttleTime 的關係就和 debounce & debounceTime 關係一樣. 

就是多了一個 durationSelector 可以動態聲明關閉期.

 

auditTime

auditTime和 debounceTime 類似, 都是限制頻密發佈的.

區別在於, auditTime 不會 reset 觀察期. 

const subject = new Subject<string>();
subject.pipe(auditTime(3000)).subscribe(v => console.log(v)); // v3..v5
subject.next('v1'); // 拒絕
await delayAsync(1000);
subject.next('v2'); // 拒絕
await delayAsync(1000);
subject.next('v3'); // 接收
await delayAsync(1000);
subject.next('v4'); // 拒絕
await delayAsync(1000);
subject.next('v5'); // 接收

觀察期是 3 秒, 雖然 Subject 一直在發佈, 但是不會 reset 觀察期, 所以 3 秒鐘一到 v3 就接收到了. 

換成是 debounceTime 的話, v1, v2, v3, v4 都會被拒絕, 因爲每一次觀察期會不斷被 reset (一直重新觀察 3 秒)

 

audit

audit & auditTime 的關係就和 debounce & debounceTime 關係一樣. 

就是多了一個 durationSelector 可以動態聲明觀察期.

 

debounceTime, throttleTime, auditTime 總結

debounceTime (delay and keep postpone) : 發佈...進入觀察期(不馬上接收)...再發布...丟棄上一個, 重新觀察期(不馬上接收)...觀察期結束(接收)

throttleTime (immediately + skip) : 發佈...關閉期(馬上接收)...再發布...skip...關閉期結束...再發布...關閉期(馬上接收)...循環

auditTime (delay only, no keep postpone) : 發佈..觀察期(不馬上接收)...再發布...丟棄上一個(但不開啓新的觀察期了)...觀察期結束(接收)

 

sampleTime

sampleTime 和上面的 debounceTime 它們沒有關係. 用途不一樣.

sampleTime 是按時去取值 (取樣) 的概念

(async () => {
  const subject = new Subject();
  subject
    .pipe(sampleTime(3000))
    .subscribe(v => console.log('sample time ' + parseInt(performance.now().toString()), v));
  await delayAsync(2000);
  subject.next(1);
  await delayAsync(2000);
  subject.next(2);
  await delayAsync(5000);
  subject.next(3);
})();

sampleTime(3000) 表示每個 3 秒, 去檢查 subject 是否有發佈新值, 如果有那麼就發佈, 如果沒有就繼續等下一個 3 秒在檢查.

效果

3 秒的時候拿到 1

6 秒的時候拿到 2

9 秒的時候 subject 沒有發佈新值, 所以也就沒有發佈了

12 秒的時候拿到 3

 

sample

sample 和 sampleTime 功能是一樣的, 區別在於檢測的 timing

sampleTime 只能設定間隔時間, sample 則可以傳入任何 Observable, 所以可以傳入 click$, 這樣每次點擊的時候就去取樣.

 

沒有介紹到的 Filter Operators

ignoreElements

single

 

一句話總結

filter : 條件過濾

first : 拿第一個

last : 拿最後一個, 記得 complete

elementAt : 拿第 n 個

skip : 跳過頭 n 的

skipLast : 跳過最後 n 個, 記得 complete

skipUntil : 一直跳過, 直到 Observable 發佈

skipWhile : 跳過依據 value 

take : 只拿頭幾個, 拿夠了自動 complete

takeLast : 只拿最後幾個, 記得 complete

takeUntil : 拿頭幾個, 直到 Observable 發佈

takeWhile : 拿投幾個, 依據 value 判斷

distinct : 但凡出現過就不接收

distinctUntilChanged : 和上一個一樣就不接收

distinctUntilKeyChanged : 只是 distinctUntilChanged 語法糖

debounceTime : delay and keep postpone

debounce : dynamic 控制 time

throttleTime immediately + skip

throttle : dynamic 控制 time

auditTime : delay only, no keep postpone

audit : dynamic 控制 time

sampleTime : 定時取樣, Observable 沒有發佈就 skip

sample : 控制 time

 

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