RxJS 系列之三 - Operators 詳解

semlinker發表於2017-04-03

RxJS 系列目錄

Marble diagrams

我們把描繪 Observable 的圖稱為 Marble diagrams,我們用 - 來表示一小段時間,這些 - 串起來就表示一個 Observable 物件。

----------------複製程式碼

X 表示有錯誤發生

---------------X複製程式碼

| 表示 Observable 結束

----------------|複製程式碼

在時間序列中,我們可能會持續發出值,如果值是數字則直接用阿拉伯數字表示,其它資料型別使用相近的英文符號表示,接下來我們看一下 interval 操作符對應的 marble 圖:

var source = Rx.Observable.interval(1000);複製程式碼

source 對應的 marble 圖:

-----0-----1-----2-----3--...複製程式碼

當 observable 同步傳送值時,如使用 of 操作符建立如下 Observable 物件:

var source = Rx.Observable.of(1,2,3,4);複製程式碼

source 對應的 marble 圖:

(1234)|複製程式碼

小括號表示同步發生。

另外 marble 圖也能夠表示 operator 的前後轉換關係,例如:

var source = Rx.Observable.interval(1000);
var newest = source.map(x => x + 1);複製程式碼

對應的 marble 圖如下:

source: -----0-----1-----2-----3--...
            map(x => x + 1)
newest: -----1-----2-----3-----4--...複製程式碼

通過 marble 圖,可以幫助我們更好地理解 operator。

詳細的資訊可以參考 - RxMarbles

Creation Operators

repeat

repeat 操作符簽名

public repeat(count: number): Observable複製程式碼

repeat 操作符作用:

重複 count 次,源 Observable 發出的值。

repeat 操作符示例:

var source = Rx.Observable.from(['a','b','c'])
               .zip(Rx.Observable.interval(500), (x,y) => x);

var example = source.repeat(2);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : ----a----b----c|
            repeat(2)
example: ----a----b----c----a----b----c|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

a
b
c
a
b
c
complete複製程式碼

JSBin - repeat

Transformation Operators

map

map 操作符簽名

public map(project: function(value: T, index: number): R, thisArg: any): Observable<R>複製程式碼

map 操作符作用:

對 Observable 物件發出的每個值,使用指定的 project 函式,進行對映處理。

map 操作符示例:

var source = Rx.Observable.interval(1000);
var newest = source.map(x => x + 2); 

newest.subscribe(console.log);複製程式碼

示例 marble 圖:

source: -----0-----1-----2-----3--...
            map(x => x + 2)
newest: -----2-----3-----4-----5--...複製程式碼

以上程式碼執行後,控制檯的輸出結果:

2
3
4
...複製程式碼

mapTo

mapTo 操作符簽名

public mapTo(value: any): Observable複製程式碼

mapTo 操作符作用:

對 Observable 物件發出的每個值,對映成固定的值。

mapTo 操作符示例:

var source = Rx.Observable.interval(1000);
var newest = source.mapTo(2); 

newest.subscribe(console.log);複製程式碼

示例 marble 圖:

source: -----0-----1-----2-----3--...
                mapTo(2)
newest: -----2-----2-----2-----2--...複製程式碼

以上程式碼執行後,控制檯的輸出結果:

2
2
2
...複製程式碼

scan

scan 操作符簽名

public scan(accumulator: function(acc: R, value: T, index: number): R,
    seed: T | R): Observable<R>複製程式碼

scan 操作符作用:

對 Observable 發出值,執行 accumulator 指定的運算,可以簡單地認為是 Observable 版本的 Array.prototype.reduce

scan 操作符示例:

var source = Rx.Observable.from('hello')
             .zip(Rx.Observable.interval(600), (x, y) => x);

var example = source.scan((origin, next) => origin + next, '');

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : ----h----e----l----l----o|
    scan((origin, next) => origin + next, '')
example: ----h----(he)----(hel)----(hell)----(hello)|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

h
he
hel
hell
hello
complete複製程式碼

(備註:scan 與 reduce 最大的差別就是 scan 最終返回的一定是一個 Observable 物件,而 reduce 的返回型別不是固定的)

JSBin - scan

buffer

buffer 操作符簽名

public buffer(closingNotifier: Observable<any>): Observable<T[]>複製程式碼

buffer 操作符作用:

緩衝源 Observable 物件已發出的值,直到 closingNotifier 觸發後,才統一輸出快取的元素。

buffer 操作符示例:

var source = Rx.Observable.interval(300);
var source2 = Rx.Observable.interval(1000);
var example = source.buffer(source2);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : --0--1--2--3--4--5--6--7..
source2: ---------0---------1--------...
            buffer(source2)
example: ---------([0,1,2])---------([3,4,5])複製程式碼

以上程式碼執行後,控制檯的輸出結果:

[0,1,2]
[3,4,5]
[6,7,8]
....複製程式碼

bufferTime

bufferTime 操作符簽名

public bufferTime(bufferTimeSpan: number, bufferCreationInterval: number, 
       maxBufferSize: number, scheduler: Scheduler): Observable<T[]>複製程式碼

bufferTime 操作符作用:

設定源 Observable 物件已發出的值的緩衝時間。

bufferTime 操作符示例:

var source = Rx.Observable.interval(300);
var example = source.bufferTime(1000);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : --0--1--2--3--4--5--6--7..
            bufferTime(1000)
example: ---------([0,1,2])---------([3,4,5])複製程式碼

以上程式碼執行後,控制檯的輸出結果:

[0,1,2]
[3,4,5]
[6,7,8]
....複製程式碼

JSBin - bufferTime

bufferCount

bufferCount 操作符簽名

public bufferCount(bufferSize: number, startBufferEvery: number):     
        Observable<T[]>複製程式碼

bufferCount 操作符作用:

緩衝源 Observable物件已發出的值,直到大小達到給定的最大 bufferSize 。

bufferCount 操作符示例:

var source = Rx.Observable.interval(300);
var example = source.bufferCount(3);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : --0--1--2--3--4--5--6--7..
            bufferCount(3)
example: ---------([0,1,2])---------([3,4,5])複製程式碼

以上程式碼執行後,控制檯的輸出結果:

[0,1,2]
[3,4,5]
[6,7,8]
....複製程式碼

concatMap

concatMap 操作符簽名

public concatMap(project: function(value: T, ?index: number): ObservableInput, 
    resultSelector: function(outerValue: T, innerValue: I, outerIndex: number, 
    innerIndex: number): any): Observable複製程式碼

concatMap 操作符作用:

對每個 Observable 物件發出的值,進行對映處理,並進行合併。該操作符也會先處理前一個 Observable 物件,在處理下一個 Observable 物件。

concatMap 操作符示例:

var source = Rx.Observable.fromEvent(document.body, 'click');

var example = source.concatMap(
                e => Rx.Observable.interval(100).take(3));

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : -----------c--c------------------...
        concatMap(c => Rx.Observable.interval(100).take(3))
example: -------------0-1-2-0-1-2---------...複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0
1
2
0
1
2複製程式碼

concatMap 其實就是 map 加上 concatAll 的簡化寫法。

JSBin - concatMap

switchMap

switchMap 操作符簽名

public switchMap(project: function(value: T, ?index: number): ObservableInput, 
  resultSelector: function(outerValue: T, innerValue: I, outerIndex: number, 
  innerIndex: number): any): Observable複製程式碼

switchMap 操作符作用:

對源 Observable 物件發出的值,做對映處理。若有新的 Observable 物件出現,會在新的 Observable 物件發出新值後,退訂前一個未處理完的 Observable 物件。

switchMap 操作符示例:

var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source.switchMap(
                    e => Rx.Observable.interval(100).take(3));

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : -----------c--c-----------------...
        concatMap(c => Rx.Observable.interval(100).take(3))
example: -------------0--0-1-2-----------...複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0
0
1
2複製程式碼

JSBin - switchMap

Filtering Operators

filter

filter 操作符簽名

public filter(predicate: function(value: T, index: number): boolean, 
    thisArg: any): Observable複製程式碼

filter 操作符作用:

對 Observable 物件發出的每個值,作為引數呼叫指定的 predicate 函式,若該函式的返回值為 true,則表示保留該項,若返回值為 false,則捨棄該值。

filter 操作符示例:

var source = Rx.Observable.interval(1000);
var newest = source.filter(x => x % 2 === 0); 

newest.subscribe(console.log);複製程式碼

示例 marble 圖:

source: -----0-----1-----2-----3-----4-...
            filter(x => x % 2 === 0)
newest: -----0-----------2-----------4-...複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0 
2
4
...複製程式碼

JSBin - filter

take

take 操作符簽名

public take(count: number): Observable<T>複製程式碼

take 操作符作用:

用於獲取 Observable 物件發出的前 n 項值,取完後就結束。

take 操作符示例:

var source = Rx.Observable.interval(1000);
var example = source.take(3);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : -----0-----1-----2-----3--..
                take(3)
example: -----0-----1-----2|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0
1
2
complete複製程式碼

first

first 操作符簽名

public first(predicate: function(value: T, index: number, source: Observable<T>): boolean,      resultSelector: function(value: T, index: number): R, 
  defaultValue: R): Observable<T | R>複製程式碼

first 操作符作用:

用於獲取 Observable 物件發出的第一個元素,取完後就結束。

first 操作符示例:

var source = Rx.Observable.interval(1000);
var example = source.first();

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : -----0-----1-----2-----3--..
                first()
example: -----0|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0
complete複製程式碼

takeUntil

takeUntil 操作符簽名

public takeUntil(notifier: Observable): Observable<T>複製程式碼

takeUntil 操作符作用:

當 takeUntil 傳入的 notifier 發出值時,源 Observable 物件就會直接進入完成狀態。

takeUntil 操作符示例:

var source = Rx.Observable.interval(1000);
var click = Rx.Observable.fromEvent(document.body, 'click');
var example = source.takeUntil(click);  

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : -----0-----1-----2------3--
click  : ----------------------c----
                takeUntil(click)
example: -----0-----1-----2----|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0
1
2
complete複製程式碼

JSBin - takeUntil

skip

skip 操作符簽名

public skip(count: Number): Observable複製程式碼

skip 操作符作用:

跳過源 Observable 物件前 count 項,並返回新的 Observable 物件。

skip 操作符示例:

var source = Rx.Observable.interval(1000);
var example = source.skip(3);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : ----0----1----2----3----4----5--....
                    skip(3)
example: -------------------3----4----5--...複製程式碼

以上程式碼執行後,控制檯的輸出結果:

3
4
5
...複製程式碼

takeLast

takeLast 操作符簽名

public takeLast(count: number): Observable<T>複製程式碼

takeLast 操作符作用:

獲取源 Observable 物件發出的,後面 count 項的值。

takeLast 操作符示例:

var source = Rx.Observable.interval(1000).take(6);
var example = source.takeLast(2);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : ----0----1----2----3----4----5|
                takeLast(2)
example: ------------------------------(45)|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

4
5
complete複製程式碼

last

last 操作符簽名

public last(predicate: function): Observable複製程式碼

last 操作符作用:

獲取源 Observable 物件發出的最後一項的值。

last 操作符示例:

var source = Rx.Observable.interval(1000).take(6);
var example = source.last();

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : ----0----1----2----3----4----5|
                    last()
example: ------------------------------(5)|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

5
complete複製程式碼

debounceTime

debounceTime 操作符簽名

public debounceTime(dueTime: number, scheduler: Scheduler): Observable複製程式碼

debounceTime 操作符作用:

在設定的時間跨度內,若源 Observable 物件沒有再發出新值,則返回最近一次發出的值。

debounceTime 操作符示例:

var source = Rx.Observable.interval(300).take(5);
var example = source.debounceTime(1000);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : --0--1--2--3--4|
        debounceTime(1000)
example: --------------4|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

4
complete複製程式碼

debounceTime 的工作方式是每次收到元素時,它會先把元素快取住並等待給定的時間,如果等待時間內沒有收到新的元素,則返回最新的快取值。如果等待時間內,又收到新的元素,則會替換之前快取的元素,並重新開始計時。

JSBin - debounceTime

throttleTime

throttleTime 操作符簽名

public throttleTime(duration: number, scheduler: Scheduler): Observable<T>複製程式碼

throttleTime 操作符作用:

從源 Observable 物件發出第一個值開始,忽略等待時間內發出的值,等待時間過後再發出新值。與 debounceTime 不同的是,throttleTime 一開始就會發出值,在等待時間內不會發出任何值,等待時間過後又會發出新的值。

throttleTime 示例:

var source = Rx.Observable.interval(300).take(5);
var example = source.throttleTime(1000);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : --0--1--2--3--4|
        throttleTime(1000)
example: --0------------4|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0
4
complete複製程式碼

throttle 比較像是控制行為的最高頻率,也就是說如果我們設定 1000 ms,那麼該事件最大頻率就是每秒觸發一次而不會過快。debounce 則比較像是必須等待的時間,要等一定的時間過了才會收到元素。

JSBin - throttleTime

distinct

distinct 操作符簽名

public distinct(keySelector: function, flushes: Observable): Observable複製程式碼

distinct 操作符的作用:

過濾源 Observable 發出的值,確保不會發出重複出現的值。

distinct 操作符示例:

var source = Rx.Observable.from(['a', 'b', 'c', 'a', 'b'])
                .zip(Rx.Observable.interval(300), (x, y) => x);
var example = source.distinct()

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : --a--b--c--a--b|
            distinct()
example: --a--b--c------|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

a
b
c
complete複製程式碼

distinct 內部會建立一個 Set 集合,當接收到元素時,會判斷 Set 集合中,是否已存在相同的值,如果已存在的話,就不會發出值。若不存在的話,會把值存入到 Set 集合中併發出該值。所以儘量不要直接把 distinct 操作符應用在無限的 Observable 物件中,這樣會導致 Set 集合越來越大。針對這種場景,大家可以設定 distinct 的第二個引數 (清除已儲存的資料),或使用 distinctUntilChanged。

JSBin - distinct

distinctUntilChanged

distinctUntilChanged 操作符簽名

public distinctUntilChanged(compare: function): Observable複製程式碼

distinctUntilChanged 操作符作用:

過濾源 Observable 發出的值,若當前發出的值與前一次值不一致,則發出該值。

distinctUntilChanged 操作符示例:

var source = Rx.Observable.from(['a', 'b', 'c', 'c', 'b'])
               .zip(Rx.Observable.interval(300), (x, y) => x);
var example = source.distinctUntilChanged()

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : --a--b--c--c--b|
            distinctUntilChanged()
example: --a--b--c-----b|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

a
b
c
b
complete複製程式碼

distinctUntilChanged 跟 distinct 一樣會把相同的元素過濾掉,但 distinctUntilChanged 只會跟最後一次送出的元素比較,不會每個比較。

JSBin - distinctUntilChanged

Combination Operators

concat

concat 操作符簽名

public concat(other: ObservableInput, scheduler: Scheduler): Observable複製程式碼

concat 操作符作用:

把多個 Observable 物件合併為一個 Observable 物件,Observable 物件會依次執行,即需等前一個 Observable 物件完成後,才會繼續訂閱下一個。

concat 操作符示例:

var source = Rx.Observable.interval(1000).take(3);
var source2 = Rx.Observable.of(3)
var source3 = Rx.Observable.of(4,5,6)
var example = source.concat(source2, source3);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : ----0----1----2|
source2: (3)|
source3: (456)|
            concat()
example: ----0----1----2(3456)|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0 # source
1 # source
2 # source
3 # source2
4 # source3
5 # source3
6 # source3
complete # example複製程式碼

JSBin - concat

concatAll

concatAll 操作符簽名

public concatAll(): Observable複製程式碼

concatAll 操作符作用:

合併多個 Observable 物件,並在上一個 Observable 物件完成後訂閱下一個 Observable 物件。

concatAll 操作符示例:

var obs1 = Rx.Observable.interval(1000).take(5);
var obs2 = Rx.Observable.interval(500).take(2);
var obs3 = Rx.Observable.interval(2000).take(1);

var source = Rx.Observable.of(obs1, obs2, obs3);

var example = source.concatAll();

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : (o1                 o2      o3)|
           \                  \       \
            --0--1--2--3--4|   -0-1|   ----0|

                concatAll()        

example: --0--1--2--3--4-0-1----0|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0 # o1
1 # o1
2 # o1
3 # o1
4 # o1
0 # o2
1 # o2
0 # o3
complete # o3複製程式碼

JSBin - concatAll

startWith

startWith 操作符簽名

public startWith(values: ...T, scheduler: Scheduler): Observable複製程式碼

startWith 操作符作用:

在開始發出源 Observable 資料之前發出已設定的引數值,並返回新的 Observable 物件。

startWith 操作符示例:

var source = Rx.Observable.interval(1000);
var example = source.startWith(0);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : ----0----1----2----3--...
                startWith(0)
example: (0)----0----1----2----3--...複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0
0
1
2
...複製程式碼

(備註:startWith 的值一開始是同步發出的,該操作符常用於儲存程式的初始狀態)

merge

merge 操作符簽名

public merge(other: ObservableInput, concurrent: number, scheduler: Scheduler): Observable複製程式碼

merge 操作符作用:

合併 Observable 物件,並按給定的時序發出對應值。

merge 操作符示例:

var source = Rx.Observable.interval(500).take(3);
var source2 = Rx.Observable.interval(300).take(6);
var example = source.merge(source2);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : ----0----1----2|
source2: --0--1--2--3--4--5|
            merge()
example: --0-01--21-3--(24)--5|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0 # source2
0 # source
1 # source2
2 # source2
1 # source
3 # source2
2 # source
4 # source2
5 # source2
complete複製程式碼

(備註:注意與 concat 操作符的區別,concat 會在前一個 Observable 物件執行完後,再訂閱下一個 Observable 物件)

JSBin - merge

mergeAll

mergeAll 操作符簽名

public mergeAll(concurrent: number): Observable複製程式碼

mergeAll 操作符作用:

將高階 Observable 物件轉換為一階Observable 物件,並同時處理所有的 Observable 物件。

mergeAll 操作符示例:

var click = Rx.Observable.fromEvent(document.body, 'click');
var source = click.map(e => Rx.Observable.interval(1000));

var example = source.mergeAll();
example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

click  : ---------c-c------------------c--.. 
        map(e => Rx.Observable.interval(1000))
source : ---------o-o------------------o--..
                   \ \                  \----0----1--...
                    \ ----0----1----2----3----4--...
                     ----0----1----2----3----4--...
                     mergeAll()
example: ----------------00---11---22---33---(04)4--...複製程式碼

以上程式碼執行後,控制檯的輸出結果:

00
11
22
33
04
4複製程式碼

mergeAll 不會像 switch 那樣退訂原有的 Observable 物件,而是會並行處理多個 Observable 物件。

JSBin - mergeAll

combineLatest

combineLatest 操作符簽名

public combineLatest(other: ObservableInput, project: function): Observable複製程式碼

combineLatest 操作符作用:

用於合併輸入的 Observable 物件,當源 Observable 物件和 other Observable 物件都發出值後,才會呼叫 project 函式。

combineLatest 操作符示例:

var source = Rx.Observable.interval(500).take(3);
var newest = Rx.Observable.interval(300).take(6);

var example = source.combineLatest(newest, (x, y) => x + y);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : ----0----1----2|
newest : --0--1--2--3--4--5|

    combineLatest(newest, (x, y) => x + y);

example: ----01--23-4--(56)--7|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0
1
2
3
4
5
6
7
complete複製程式碼

combineLatest 示例執行過程 (project -> (x, y) => x + y):

  • newest 發出 0 ,但此時 source 並未發出任何值,所以不會呼叫 project 函式
  • source 發出 0 ,此時 newest 最後一次發出的值為 0 ,呼叫 project 函式,返回值為 0
  • newest 發出 1 ,此時 source 最後一次發出的值為 0,呼叫 project 函式,返回值為 1
  • newest 發出 2 ,此時 source 最後一次發出的值為 0,呼叫 project 函式,返回值為 2
  • source 發出 1 ,此時 newest 最後一次發出的值為 2 ,呼叫 project 函式,返回值為 3
  • newest 發出 3 ,此時 source 最後一次發出的值為 1,呼叫 project 函式,返回值為 4
  • source 發出 2 ,此時 newest 最後一次發出的值為 3 ,呼叫 project 函式,返回值為 5
  • newest 發出 4 ,此時 source 最後一次發出的值為 2,呼叫 project 函式,返回值為 6
  • newest 發出 5 ,此時 source 最後一次發出的值為 2,呼叫 project 函式,返回值為 7
  • newest 和 source 都結束了,所以 example 也結束了。

JSBin - combineLatest

zip

zip 操作符簽名

public static zip(observables: *,project: Function): Observable<R>複製程式碼

zip 操作符作用:

根據每個輸入 Observable 物件的輸出順序,產生一個新的 Observable 物件。

zip 操作符示例:

var source = Rx.Observable.interval(500).take(3);
var newest = Rx.Observable.interval(300).take(6);

var example = source.zip(newest, (x, y) => x + y);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : ----0----1----2|
newest : --0--1--2--3--4--5|
    zip(newest, (x, y) => x + y)
example: ----0----2----4|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0
2
4
complete複製程式碼

zip 示例執行過程 (project -> (x, y) => x + y):

  • newest 發出第一個值 0 ,此時 source 並未發出任何值,所以不會呼叫 project 函式
  • source 發出第一個值 0 ,此時 newest 之前發出的第一個值為 0,呼叫 project 函式,返回值為 0
  • newest 發出第二個值 1 ,此時 source 並未發出第二個值,所以不會呼叫 project 函式
  • newest 發出第三個值 2 ,此時 source 並未發出第三個值,所以不會呼叫 project 函式
  • source 發出第二個值 1 ,此時 newest 之前發出的第二個值為 1,呼叫 project 函式,返回值為 2
  • newest 發出第四個值 3 ,此時 source 並未發出第四個值,所以不會呼叫 project 函式
  • source 發出第三個值 2 ,此時 newest 之前發出的第三個值為 2,呼叫 project 函式,返回值為 4
  • source 物件結束,example 物件也同時結束,因為 source 物件與 newest 物件不會再有相同次序的值

JSBin - zip

withLatestFrom

withLatestFrom 操作符簽名

public withLatestFrom(other: ObservableInput, project: Function): Observable複製程式碼

withLatestFrom 操作符作用:

當源 Observable 發出新值的時候,根據 project 函式,合併 other Observable 物件此前發出的最新值。

withLatestFrom 操作符示例:

var main = Rx.Observable.from('hello').zip(Rx.Observable.interval(500), 
    (x, y) => x);
var some = Rx.Observable.from([0,1,0,0,0,1]).zip(Rx.Observable.interval(300), 
    (x, y) => x);

var example = main.withLatestFrom(some, (x, y) => {
    return y === 1 ? x.toUpperCase() : x;
});

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

main   : ----h----e----l----l----o|
some   : --0--1--0--0--0--1|

withLatestFrom(some, (x, y) =>  y === 1 ? x.toUpperCase() : x);

example: ----h----e----l----L----O|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

h
e
l
L
O
complete複製程式碼

withLatestFrom 示例執行過程 (project -> (x, y) => y === 1 ? x.toUpperCase() : x) ):

  • main 發出 h ,此時 some 上一次發出的值為 0,呼叫 project 函式,返回值為 h
  • main 發出 e ,此時 some 上一次發出的值為 0,呼叫 project 函式,返回值為 e
  • main 發出 l ,此時 some 上一次發出的值為 0,呼叫 project 函式,返回值為 l
  • main 發出 l,此時 some 上一次發出的值為 1,呼叫 project 函式,返回值為 L
  • main 發出 o,此時 some 上一次發出的值為 1,呼叫 project 函式,返回值為 O

JSBin - withLatestFrom

switch

switch 操作符簽名

public switch(): Observable<T>複製程式碼

switch 操作符作用:

切換為最新的 Observable 資料來源,並退訂前一個 Observable 資料來源。

switch 操作符示例:

var click = Rx.Observable.fromEvent(document.body, 'click');
var source = click.map(e => Rx.Observable.interval(1000));

var example = source.switch();
example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

click  : ---------c-c------------------c--.. 
        map(e => Rx.Observable.interval(1000))
source : ---------o-o------------------o--..
                   \ \                  \----0----1--...
                    \ ----0----1----2----3----4--...
                     ----0----1----2----3----4--...
                     switch()
example: -----------------0----1----2--------0----1--...複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0
1
2
0
1
...複製程式碼

從 switch 操作符示例的 marble 圖,可以看得出來第一次點選事件與第二次點選事件時間點太靠近了,導致第一個 Observable 還來不及發出值就直接被退訂了,當每次點選後建立新的 Observable 物件,就會自動退訂前一次建立的 Observable 物件。

switch 操作符會在新的 Observable 物件建立完後,直接使用新的 Observable 物件,並會自動退訂之前舊的 Observable 物件。

JSBin - switch

Utility Operators

delay

delay 操作符簽名

public delay(delay: number | Date, scheduler: Scheduler): Observable複製程式碼

delay 操作符作用:

延遲源 Observable 物件,發出第一個元素的時間點。

delay 操作符使用示例:

var source = Rx.Observable.interval(300).take(5);
var example = source.delay(500);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : --0--1--2--3--4|
        delay(500)
example: -------0--1--2--3--4|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0 # 500ms後發出
1
2
3
4
complete複製程式碼

JSBin - delay

delayWhen

delayWhen 操作符簽名

public delayWhen(delayDurationSelector: function(value: T): Observable, 
            subscriptionDelay: Observable): Observable複製程式碼

delayWhen 操作符作用:

delayWhen 的作用跟 delay 操作符類似,最大的區別是 delayWhen 會影響每個元素,而且呼叫的時候需要設定 delayDurationSelector 函式,該函式的返回值是 Observable 物件。

delayWhen 操作符示例:

var source = Rx.Observable.interval(300).take(5);
var example = source
              .delayWhen( x => Rx.Observable.interval(100 * x).take(1));

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : --0--1--2--3--4|
    .delayWhen(x => Rx.Observable.interval(100 * x).take(1));
example: --0---1----2-----3------4|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

0
1
2
3
4
complete複製程式碼

Multicasting Operators

multicast

multicast 操作符簽名

public multicast(subjectOrSubjectFactory: Function | Subject, 
    selector: Function): Observable複製程式碼

multicast 操作符作用:

用於掛載 Subject 物件,並返回一個可連結 (connectable) 的 Observable 物件。

multicast 操作符示例:

var source = Rx.Observable.interval(1000)
             .take(3)
             .multicast(new Rx.Subject());

var observerA = {
    next: value => console.log('A next: ' + value),
    error: error => console.log('A error: ' + error),
    complete: () => console.log('A complete!')
};

var observerB = {
    next: value => console.log('B next: ' + value),
    error: error => console.log('B error: ' + error),
    complete: () => console.log('B complete!')
};

source.subscribe(observerA); // subject.subscribe(observerA)

source.connect(); // source.subscribe(subject)

setTimeout(() => {
    source.subscribe(observerB); // subject.subscribe(observerA)
}, 1000);複製程式碼

以上程式碼執行後,控制檯的輸出結果:

A next: 0
A next: 1
B next: 1
A next: 2
B next: 2
A complete!
B complete!複製程式碼

JSBin - multicast

上面示例中,我們通過 multicast 掛載 Subject 物件之後返回了 source 物件,該物件通過 subscribe 新增的觀察者,都是新增到 Subject 物件內部的觀察者列表中。此外當呼叫 source 物件的 connect() 方法後才會真正的訂閱 source 物件,如果沒有執行 connect() ,source 不會真正執行。如果要真正退訂觀察者,應該使用以下方式:

var realSubscription = source.connect();
...
realSubscription.unsubscribe();複製程式碼

refCount

refCount 必須搭配 multicast 一起使用,在呼叫 multicast 操作符後,接著呼叫 refCount() 。這樣只要有訂閱就會自動進行 connect (連結) 操作。具體示例如下:

var source = Rx.Observable.interval(1000)
             .do(x => console.log('send: ' + x))
             .multicast(new Rx.Subject())
             .refCount();

var observerA = {
    next: value => console.log('A next: ' + value),
    error: error => console.log('A error: ' + error),
    complete: () => console.log('A complete!')
};

var observerB = {
    next: value => console.log('B next: ' + value),
    error: error => console.log('B error: ' + error),
    complete: () => console.log('B complete!')
};

var subscriptionA = source.subscribe(observerA); // 訂閱數 0 => 1

var subscriptionB;
setTimeout(() => {
    subscriptionB = source.subscribe(observerB);  // 訂閱數 1 => 2
}, 1000);複製程式碼

上面示例中,當 source 物件被觀察者 A 訂閱時,就會立即執行併傳送值,我們就不需要再額外執行 connect 操作。同樣只要訂閱數變成 0,就會自動停止傳送。具體示例如下:

var source = Rx.Observable.interval(1000)
             .do(x => console.log('send: ' + x))
             .multicast(new Rx.Subject())
             .refCount();

var observerA = {
    next: value => console.log('A next: ' + value),
    error: error => console.log('A error: ' + error),
    complete: () => console.log('A complete!')
};

var observerB = {
    next: value => console.log('B next: ' + value),
    error: error => console.log('B error: ' + error),
    complete: () => console.log('B complete!')
}

var subscriptionA = source.subscribe(observerA);
// 訂閱數 0 => 1

var subscriptionB;
setTimeout(() => {
    subscriptionB = source.subscribe(observerB);
    // 訂閱數 1 => 2
}, 1000);

setTimeout(() => {
    subscriptionA.unsubscribe(); // 訂閱數 2 => 1
    subscriptionB.unsubscribe(); // 訂閱數 1 => 0,source 停止傳送元素
}, 5000);複製程式碼

以上程式碼執行後,控制檯的輸出結果:

send: 0
A next: 0
send: 1
A next: 1
B next: 1
send: 2
A next: 2
B next: 2
send: 3
A next: 3
B next: 3
send: 4
A next: 4
B next: 4複製程式碼

JSBin - refCount

publish

publish 操作符簽名

public publish(selector: Function): *複製程式碼

publish 操作符作用:

用於掛載 Subject 物件,並返回一個可連結 (connectable) 的 Observable 物件。即 publish 操作符與 multicast(new Rx.Subject()) 是等價的。

var source = Rx.Observable.interval(1000)
             .publish() 
             .refCount();

var source = Rx.Observable.interval(1000)
             .multicast(new Rx.Subject()) 
             .refCount();複製程式碼

publishReplay

var source = Rx.Observable.interval(1000)
             .publishReplay(1) 
             .refCount();

var source = Rx.Observable.interval(1000)
            .multicast(new Rx.ReplaySubject(1)) 
            .refCount();複製程式碼

publishBehavior

var source = Rx.Observable.interval(1000)
             .publishBehavior(0) 
             .refCount();

var source = Rx.Observable.interval(1000)
             .multicast(new Rx.BehaviorSubject(0)) 
             .refCount();複製程式碼

publishLast

var source = Rx.Observable.interval(1000)
             .publishLast() 
             .refCount();

var source = Rx.Observable.interval(1000)
             .multicast(new Rx.AsyncSubject(1)) 
             .refCount();複製程式碼

share

share 操作符簽名

public share(): Observable<T>複製程式碼

share 操作符作用:

share 操作符是 publish + refCount 的簡寫。

share 操作符示例:

var source = Rx.Observable.interval(1000)
             .share();

var source = Rx.Observable.interval(1000)
             .publish()
             .refCount();

var source = Rx.Observable.interval(1000)
             .multicast(new Rx.Subject()) 
             .refCount();複製程式碼

Error Handling Operators

catch

catch 操作符簽名

public catch(selector: function): Observable複製程式碼

catch 操作符作用:

用於捕獲異常,同時可以返回一個 Observable 物件,用於發出新的值。

catch 操作符示例:

var source = Rx.Observable.from(['a','b','c','d',2])
               .zip(Rx.Observable.interval(500), (x,y) => x);

var example = source.map(x => x.toUpperCase())
                    .catch(error => Rx.Observable.of('h'));

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : ----a----b----c----d----2|
        map(x => x.toUpperCase())
         ----a----b----c----d----X|
        catch(error => Rx.Observable.of('h'))
example: ----A----B----C----D----h|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

A
B
C
D
h
complete複製程式碼

當錯誤發生時,我們可以返回一個 empty 的 Observable 物件來直接結束源 Observable 物件。

retry

retry 操作符簽名

public retry(count: number): Observable複製程式碼

retry 操作符作用:

發生錯誤後,重試 count 次數

retry 操作符示例:

var source = Rx.Observable.from(['a','b','c','d',2])
               .zip(Rx.Observable.interval(500), (x,y) => x);

var example = source.map(x => x.toUpperCase())
                    .retry(1);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : ----a----b----c----d----2|
        map(x => x.toUpperCase())
         ----a----b----c----d----X|
                retry(1)
example: ----A----B----C----D--------A----B----C----D----X|複製程式碼

以上程式碼執行後,控制檯的輸出結果:

A
B
C
D
A
B
C
D
Error: TypeError: x.toUpperCase is not a function複製程式碼

retryWhen

retryWhen 操作符簽名

public retryWhen(notifier: function(errors: Observable): Observable): Observable複製程式碼

retryWhen 操作符作用:

捕獲異常 Observable 物件,進行異常處理後,可重新訂閱源 Observable 物件。

retryWhen 操作符示例:

var source = Rx.Observable.from(['a','b','c','d',2])
               .zip(Rx.Observable.interval(500), (x,y) => x);

var example = source.map(x => x.toUpperCase())
                    .retryWhen(errorObs => errorObs.delay(1000));

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});複製程式碼

示例 marble 圖:

source : ----a----b----c----d----2|
        map(x => x.toUpperCase())
         ----a----b----c----d----X|
        retryWhen(errorObs => errorObs.delay(1000))
example: ----A----B----C----D-------------------A----B----C----D----...複製程式碼

以上程式碼執行後,控制檯的輸出結果:

A
B
C
D
...複製程式碼

參考資源

相關文章