RxJs 常用運算子介紹

前言

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

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

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

建立Observable

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

from

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

of

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

fromEvent

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

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更加的原子化!

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裡一定會見到

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

filter

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

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

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語意更明顯,寫法更精簡

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

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教學系列文章