[RxJS] Cold Observable v.s Hot Observable

|

昨天我們介紹了 ObservableSubject 系列兩種不同的建立資料流物件的方法,也稍微說明了訂閱時會發生的不同,今天讓我們用比較專有的名詞:Cold Observable 和 Hot Observable 來說明這兩種的差別。從運作的方式去了解,能更幫助我們在開發時更加掌握技術的使用方式!

今天的實作程式碼:https://stackblitz.com/edit/mastering-rxjs-13-warm-observable

Cold Observable

先來回顧一下 Observable 類別建立及使用方式:

const source$ = new Observable(subscriber => {
  console.log('stream 開始');
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  subscriber.next(4);
  console.log('steam 結束');
  subscriber.complete();
});
 
source$.subscribe(data => console.log(`Observable 第一次訂閱: ${data}`));
// 1, 2, 3, 4
source$.subscribe(data => console.log(`Observable 第二次訂閱: ${data}`));
// 1, 2, 3, 4

對於每次訂閱來說,都是一次新的資料流產生;我們可以把整個資料流當作是一條水管管線設計圖,每次訂閱時,都是依照這條管線組裝出新的水管線路,然後裝水打開開關讓水流出,這樣的好處是每次訂閱都是新資料流的所以不會互相影響

這種每次訂閱都重新開始的流程,稱為 「Cold Observable」。Cold 通常代表被用到時才啟動的行為,在這裡也是如此,說明了這個 Observable 被訂閱時才會開始啟動新的資料流。

以一般的觀察者模式的行為來說,每次事件的發生都是「推送」給觀察者 (Observer) 的;而 Cold Observable 每次訂閱後就只會有一個觀察者,下一個觀察者要進行訂閱時會是一次新的資料流程,因此 Cold Observable 與 observer 是「一對一」的關係,這種只會推給唯一一個觀察者的方式也稱為 「unicast」。

先預告一下,下一篇開始我們會介紹各種建立資料流 的 operators,這類 operators 基本上都是屬於 Cold Observable,我們到時候再來看看。

Hot Observable

Subject 系列產生出來的資料流都是屬於 Hot Observable,一樣的先來回顧 Subject 的使用方式:

const source$ = new Subject();

source$.subscribe(data => console.log(`Subject 第一次訂閱: ${data}`));
// 1, 2, 3, 4
  
source$.next(1);
source$.next(2);

source$.subscribe(data => console.log(`Subject 第二次訂閱: ${data}`));
// 3, 4

source$.next(3);
source$.next(4);

source$.subscribe(data => console.log(`Subject 第三次訂閱: ${data}`));
// (沒收到任何事件就結束了)
source$.complete();

一樣當作水管管線來看的話,Subject 就是一條隨時可能有資料流過的線路,每次訂閱都只是等待這條水管線路傳送資料過來而已,而這樣的好處是更有彈性,因為不用事先就把所有流程準備好,可以隨時依照不同情境在程式內讓新的事件發生 (只要呼叫 next() 就好),所有的觀察者都會及時收到這份資料。

這種資料流已經開始,隨時訂閱就是等待最新資料的流程,就稱為 「Hot Observerable」。Hot 本身就有隨時準備好的意思,因此每次訂閱時不用從頭來過,只關注最新事件即可。

由於只會有一個 Observable,在每次事件發生時都會推送給所有的 observer,因此 Hot Observable 與 Observer 的關係是「一對多」的關係,又稱為 「multicast」。

Warm Observable

有沒有辦法把 Cold Observable 轉成 Hot Observable 呢?嚴格來說並沒有,因為 Cold Observable 一定要有個「啟動」的動作才會開始資料流,而 Hot Observable 在被建立同時就是啟動的狀態了。但是我們可以透過一些 Multicast 類的 operators 來達到類似目標,例如 share()

const source$ = new Observable(subscriber => {
  console.log('stream 開始');
  setTimeout(() => subscriber.next(1), 100);
  setTimeout(() => subscriber.next(2), 200);
  setTimeout(() => subscriber.next(3), 300);
  setTimeout(() => {
    subscriber.next(4);
    subscriber.complete();
    console.log('steam 結束');
  }, 400);
});

const hotSource$ = source$.pipe(
  share()
);

setTimeout(() => {
  hotSource$.subscribe(data => console.log(`Observable 第一次訂閱: ${data}`));

  setTimeout(() => {
    hotSource$.subscribe(data => console.log(`Observable 第二次訂閱: ${data}`));
  }, 200);
}, 1000);

上述程式我們先透過 setTimeout() 讓整個 stream 傳遞比較有明顯的時間順序:

---1---2---3---(4|)

之後透過 share() operator 讓整個 Observable 變成 multicast 的 Observable,這時候整個資料流還沒開始,必須有「第一次訂閱」的動作,才會開始提供資料。由於必須要先有一次「訂閱」的動作讓它啟動,所以也稱為 Warm Observable

在程式中我們先等待一秒鐘才開始訂閱,可以藉此看到在這段時間不會印出「stream 開始」的文字,直到第一次訂閱印出。第二次訂閱則刻意再慢一點進行訂閱,已確認收到的資料並不會從頭開始。

整個執行結果如下:

有些時候我們會先設計好一個 Cold Observable,但又不希望每次訂閱時都要重新來過 (例如 HTTP 請求、WebSocket 等),就很適合把 unicast 轉成 multicast 類型的 Observable!

關於更多 multicast 類型的 operators,在之後的文章我們再來節紹。

本日小結

今天我們介紹了 Cold Observable 與 Hot Observable 的不同與應用情境,以及如何把 Cold Observable 轉換成類似 Hot Observable 的 Warm Observable。

  • Cold Observable:在每次訂閱時,完整的資料流會重新產生;資料流與訂閱者是一對一的關係。
  • Hot Observable:在訂閱前資料流已開始,每個訂閱者訂閱的都是同一個資料流;資料流與訂閱者是一對多的關係。
  • Warm Observable:在第一次訂閱開始前不會啟動資料流,直到第一次訂閱發生啟動,所有觀察者都是訂閱同一個資料流;資料流與訂閱者是一對多關係。

這些概念在需要更深入資料流程時就會非常重要,也能讓我們比較容易知道何時該使用什麼樣類型的 Observable。之後的文章我們會有機會再多說明一些。

明天開始就是各種 operators 的介紹啦!敬請期待!

如果您覺得我的文章有幫助,歡迎免費成為 LikeCoin 會員,幫我的文章拍手 5 次表示支持!