易搞混的高階運算子:switchMap, mergeMap,concatMap, exhaustMap
前言
在RxJs中,除了基本的流水線之外
流水線之間,可以再創造出RxJs自己的observable流(即支線,官方正式命名: higher-order Observable
)
雖稱為高階函式,但用資料流來看,比較像是主線與支線
由下圖來看,主流即------->
每一節點又產生出自己的小流水線(支線),最終再併回主線
前一篇的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
重點小結
重點小結:
- 使用方式是創造出
observable支線
來運算(即運算子須回傳observable
) - 官方對支線的正式命名為
higher-order Observable
- 故這類運算子又稱為高階運算子
- 這些高階運算子則是將各種支線結果壓平回主線(first-order),利於最後
.subscribe()
取用
一開始搞不清楚沒關係
只要知道當需求有需要創造支線來處理,再依需求決定用哪個xxxxxMap
即可
熟悉後,再回頭看就會漸漸理解了!
concatMap
主線持續發送事件,但支線尚未處理完成
則等待支線處理完成,再接續處理主線
interval(500).pipe( // 主線-每5秒發送事件
concatMap(_ => interval(200).pipe( // 支線-每2秒發送事件
take(5)
)
)
);
由上圖可見,每一件事情都會處理
主要使用在事情有其順序性,不可錯開,大家乖乖排隊,逐一處理
switchMap
當支線尚未處理完成,遇到主線又發出新的事件
直接截斷現有支線,重新觸發新的支線
interval(500).pipe( // 主線-每5秒發送事件
switchMap(_ => interval(200).pipe( // 支線-每2秒發送事件
take(5)
)
)
);
由上圖可以看得出來
支線處理到一半,接收到新的主線後,就馬上斷開現有事件,重新處理
Angular 應用實例
switchMap 是在Angular上最有機會使用到的函式!
舉例:輸入框輸入完Mail後,馬上發送至Server確認Mail是否存在
當輸入完內容、停頓超過500ms後,馬上發送api去查詢結果
但因網速慢或Server忙碌,過了2、3秒尚未回傳
user其實尚未輸入完,又繼續輸入
就需要截斷目等待api的行為,重新發送一次!
盡管有使用到沒提過的運算子
輔以註解後,仍可感受得到RxJs帶來的強大可讀性與易維護性
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
主線持續發送事件,但支線尚未處理完成
則併發處理
interval(500).pipe( // 主線-每5秒發送事件
mergeMap(_ => interval(200).pipe( // 支線-每2秒發送事件
take(5)
)
)
);
主要用於事情無順序性,先做完就先回傳,且每一件事都會做到
mergeMap與switchMap比較
以去超商買東西要結帳比喻
switchMap只有一個店員在結帳,大家排隊慢慢等
mergeMap則是同時有N個店員在結帳,你只要選好東西,一找到店員(發送事件),就馬上處理
exhaustMap
主線持續發送事件,但支線尚未處理完成
則忽略主線,待支線處理完成後,才繼續等待下一件事
interval(500).pipe( // 主線-每5秒發送事件
exhaustMap(_ => interval(200).pipe( // 支線-每2秒發送事件
take(5)
)
)
);
就像一個人一次只能處理一件事情,當交辦後,則全力處理
不理會外部事務,手上事情完成(支線)後,才閒置下來等待下一次的任務
使用率較低