易搞混的高階運算子:switchMap, mergeMap, concatMap, exhaustMap

前言

在RxJs中,除了基本的流水線之外
流水線之間,可以再創造出RxJs自己的observable流(即支線,官方正式命名: higher-order Observable)

雖稱為高階函式,但用資料流來看,比較像是主線與支線

由下圖來看,主流即------->
每一節點又產生出自己的小流水線(支線),最終再併回主線

rxjs-concatAll

前一篇的catchError與throwError即是一個簡單的例子
catchError接到錯誤後,須回傳observable
通常會使用throwError包裝,其實亦可使用of來包裝回傳
但使用of包裝的話,回傳結果會被next接收

catchError一定是回傳throwError
便於.subscribe()裡分離正常情況(next)與例外(error)邏輯

一般正常流水線(即主線)
可以透過switchMap, mergeMap, concatMap, exhaustMap創造支線
最後再壓平、合併回來

而這四種運算子使用率高,但易於搞混
故單開一篇專門介紹

語法的合併

這四個運算子,其實都是由map + 相應的All合併出來的寫法
因使用率高,而出現的簡化運算子

  • switchMap -> map + switchAll
  • mergeMap -> map + mergeAll
  • concatMap -> map + concatAll
  • exhaustMap -> map + exhaustAll

重點小結

重點小結:

  1. 使用方式是創造出observable支線來運算(即運算子須回傳observable)
  2. 官方對支線的正式命名為higher-order Observable
  3. 故這類運算子又稱為高階運算子
  4. 這些高階運算子則是將各種支線結果壓平回主線(first-order),利於最後.subscribe()取用

一開始搞不清楚沒關係
只要知道當需求有需要創造支線來處理,再依需求決定用哪個xxxxxMap即可
熟悉後,再回頭看就會漸漸理解了!

concatMap

主線持續發送事件,但支線尚未處理完成
等待支線處理完成,再接續處理主線

1
2
3
4
5
6
interval(500).pipe( // 主線-每5秒發送事件
concatMap(_ => interval(200).pipe( // 支線-每2秒發送事件
take(5)
)
)
);

rxviz-concatMap.png

由上圖可見,每一件事情都會處理
主要使用在事情有其順序性,不可錯開,大家乖乖排隊,逐一處理

switchMap

當支線尚未處理完成,遇到主線又發出新的事件
直接截斷現有支線,重新觸發新的支線

1
2
3
4
5
6
interval(500).pipe( // 主線-每5秒發送事件
switchMap(_ => interval(200).pipe( // 支線-每2秒發送事件
take(5)
)
)
);

rxviz-switchMap.png

由上圖可以看得出來
支線處理到一半,接收到新的主線後,就馬上斷開現有事件,重新處理

Angular 應用實例

switchMap 是在Angular上最有機會使用到的函式!
舉例:輸入框輸入完Mail後,馬上發送至Server確認Mail是否存在

當輸入完內容、停頓超過500ms後,馬上發送api去查詢結果
但因網速慢或Server忙碌,過了2、3秒尚未回傳
user其實尚未輸入完,又繼續輸入
就需要截斷目等待api的行為,重新發送一次!

盡管有使用到沒提過的運算子
輔以註解後,仍可感受得到RxJs帶來的強大可讀性與易維護性

1
2
3
4
5
6
7
8
9
10
11
private mailExistsValidator(): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
return control.valueChanges.pipe(
debounceTime(500), // 等待500ms,避免輸入中高頻率觸發
distinctUntilChanged(), // 輸入內容與前值不一致時再往後繼續處理,一致時不後送
switchMap((mail: string) => this.http.get<User>(`/user?mail=${mail}`)), // api設計為查無資料時會回404 not found,會進入catchError。當尚未回傳,又須重新發查,截斷 -> 重新發查
mapTo({mailExist: true}), // 不在乎回傳結果,直接轉換成Angular reactive Form的格式
catchError(_ => of(null)), // 當有錯誤(即api 回了404),catch住後,用of包裝成正常回傳。當null時,reactive Form會提示錯誤訊息
)
}
}

mergeMap

主線持續發送事件,但支線尚未處理完成
併發處理

1
2
3
4
5
6
interval(500).pipe( // 主線-每5秒發送事件
mergeMap(_ => interval(200).pipe( // 支線-每2秒發送事件
take(5)
)
)
);

rxviz-mergeMap.png

主要用於事情無順序性,先做完就先回傳,且每一件事都會做到

mergeMap與switchMap比較

以去超商買東西要結帳比喻

switchMap只有一個店員在結帳,大家排隊慢慢等
mergeMap則是同時有N個店員在結帳,你只要選好東西,一找到店員(發送事件),就馬上處理

exhaustMap

主線持續發送事件,但支線尚未處理完成
忽略主線,待支線處理完成後,才繼續等待下一件事

1
2
3
4
5
6
interval(500).pipe( // 主線-每5秒發送事件
exhaustMap(_ => interval(200).pipe( // 支線-每2秒發送事件
take(5)
)
)
);

rxviz-exhaustMap.png

就像一個人一次只能處理一件事情,當交辦後,則全力處理
不理會外部事務,手上事情完成(支線)後,才閒置下來等待下一次的任務

使用率較低

系列文章

RxJs教學系列文章

參考資料

用可视化来理解switchMap, concatMap, flatMap,exhaustMap