RxJs 常用運算子介紹

前言

RxJs提供相當多的Operator
官網可以查看所有Operator與使用方式
基本上只要熟悉幾個常用的,大部份的邏輯應足以處理!
剩下則邊用邊學即可

Operator的命名相當語義化,可以很明確看出其功能
經過各種組合後,就可以組出一條相當漂亮且易於維護的程式碼

之前提過RxJs就像是一條工廠流水線
可以至RxViz將流水線視覺化!
對於初學來說,會是一條很好的學習方式
尤其像mergeMap這種併發型的Operator,就連熟手有時也會混亂

建立Observable

將資料轉成Observable,常用的有下列函式
若為單純變數,不論是fromof,結果都一樣
而他們之間最大的差別就是:
from會把可迭代的(iterator)資料逐一送出
of則不論是否可迭代(iterator),一律整份送出

from

1
2
from('TEST').subscribe(console.log); // 輸出'TEST'
from([1, 2, 3]).subscribe(console.log); // 逐一輸出: 1--2--3

of

1
2
from('TEST').subscribe(console.log); // 輸出'TEST'
from([1, 2, 3]).subscribe(console.log); // 輸出完整陣列: [1, 2, 3]

fromEvent

通常是前端使用
針對特定事件(如click)轉換成Observable

1
fromEvent(document, 'click').subscribe(console.log); // 輸出 click事件的完整物件

放在pipe裡的Operator

tap

用來處理與流水線主要業務無關、但須動作的事
通常用來處理一些額外動作,但不會變動到資料集的事務
如寫log、回傳前須異動DB特定flag欄位…之類的
最大宗使用都是在寫log

在Angular裡不太會用到
因若需要debug,直接在相關邏輯裡加console.log就好了
不太會特地import debug完後又刪掉

但在後端Nest.Js就滿有機會的了,若商業邏輯完全使用RxJs實現
tap則可以將寫log之事獨立出來,將function更加的原子化!

1
2
3
4
from([1, 2, 3]).pipe(
tap(console.log), // (靜態寫法)印出1, 2, 3
tap(value => console.log(vaule)), // (傳參寫法)印出1, 2, 3
).subscribe();

map

RxJs裡最常使用的運算子!
不知道該用什麼運算子,用map就對了
自己墊邏輯進去一切都可做

命名也很直覺,在各語言functional programming裡一定會見到

1
2
3
4
5
6
7
8
from([1, 2, 3]).pipe(
map(val => {
if (val < 2) {
return val;
}
return null;
})
).subscribe(console.log); // 逐一輸出: 1, null, null

filter

第二常使用的運算子,用以過濾掉指定條件的資料(或取出特定資料)
一樣在各語言functional programming裡也會見到

1
2
3
4
5
6
7
8
9
from([1, 2, 3]).pipe(
map(val => {
if (val < 2) {
return val;
}
return null;
}),
filter(val => val !== null)
).subscribe(console.log); // 逐一輸出: 1

與takeWhile, takeUntil的區別

從函式命名上可以感覺與filter很像,但似乎比filter命名更加精確!
條件符合時才拿取(take),但實際使用時,卻會覺得怪怪的
怎麼只抓一次就不抓了?!

takefilter最大的區別在於:取完後就complete了!
在RxJs中,complete就代表不在「觀察」
永遠不會再接收、處理任何資料!

所以大部份情況下還是乖乖用filter就好嘍!

toArray與groupBy

在後端做資料分組時較易用上
前端反而不太用到
而這2個很常組合使用
搭配mergeMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const array = [
{id: 1, shopId: 'a', category: 'AAA', name: 'Andy', birth: '1990/06/25'},
{id: 2, shopId: 'b', category: 'BBB', name: 'Ben', birth: '1989/01/01'},
{id: 3, shopId: 'a', category: 'AXX', name: 'Amy', birth: '1990/06/01'},
{id: 4, shopId: 'b', category: 'BXX', name: 'Brandon', birth: '1989/05/06'},
{id: 5, shopId: 'a', category: 'AAA', name: 'Anderson', birth: '1991/12/06'},
];

from(array).pipe(
groupBy(val => val.shopId),
mergeMap(group => group.pipe(toArray())
);

// 得到結果如下:
[
[
{id: 1, shopId: 'a', category: 'AAA', name: 'Andy', birth: '1990/06/25'},
{id: 3, shopId: 'a', category: 'AXX', name: 'Amy', birth: '1990/06/01'},
{id: 5, shopId: 'a', category: 'AAA', name: 'Anderson', birth: '1991/12/06'},
],
[
{id: 2, shopId: 'b', category: 'BBB', name: 'Ben', birth: '1989/01/01'},
{id: 4, shopId: 'b', category: 'BXX', name: 'Brandon', birth: '1989/05/06'}
]
]

進階用法可參考補充資料:RxJs groupBy 多條件分組

mapTo

不在乎取得結果,直接強制轉成特定值
當然用map亦可做到,但mapTo語意更明顯,寫法更精簡

1
2
3
4
fromEvent(document, 'click').pipe(
// map(event => 'clicked!'), // map 寫法會長這樣,省掉了暱名函式!
mapTo('clicked!') // 不在乎點擊事件的event內容,一律轉成字串'clicked!'
).subscribe(console.log);

throwError與catchError

相當於語言裡的try-catch寫法
throwError則是將錯誤轉換成Observable,供Observer裡的error接收
通常會搭配catchError一起出現

要注意的是:
catchError接到後,須回傳Observable

1
2
3
4
5
6
7
8
9
10
11
12
from([1, 2, 3, 4, 5]).pipe(
map(value => {
if (value < 3) {
return value;
}
throw new Error('大於3了!!');

}),
catchError(error => throwError(error)),
).subscribe(
result => console.log(result), // 輸出1 -> 2
error => console.log(`errorMessage: ${error}`)); // 輸出「errorMessage: 大於3了!!」

系列文章

RxJs教學系列文章