[譯] 熱的 Vs 冷的 Observables

SangKa發表於2017-08-19

原文連結: medium.com/@benlesh/ho…
本文為 RxJS 中文社群 翻譯文章,如需轉載,請註明出處,謝謝合作!
如果你也想和我們一起,翻譯更多優質的 RxJS 文章以奉獻給大家,請點選【這裡】

TL;DR: 當不想一遍又一遍地建立生產者( producer )時,你需要熱的 Observable 。

冷的是指 Observable 建立了生產者

// 冷的
var cold = new Observable((observer) => {
  var producer = new Producer();
  // observer 會監聽 producer
});複製程式碼

熱的是指 Observable 複用生產者

// 熱的
var producer = new Producer();
var hot = new Observable((observer) => {
  // observer 會監聽 producer
});複製程式碼

深入瞭解發生了什麼...

我最新的文章通過構建 Observable 來學習 Observable 主要是為了說明 Observable 只是函式。這篇文章的目標是為了揭開 Observable 自身的神祕面紗,但它並沒有真正深入到 Observable 讓初學者最容易困惑的問題: “熱”與“冷”的概念。

Observables 只是函式而已!

Observables 是將觀察者和生產者聯絡起來的函式。 僅此而已。它們並不一定要建立生產者,它們只需建立觀察者來監聽生產者,並且通常會返回一個拆卸機制來刪除該監聽器。

什麼是“生產者”?

生產者是 Observable 值的來源。它可以是 Web Socket、DOM 事件、迭代器或在陣列中迴圈的某種東西。基本上,這是你用來獲取值的任何東西,並將它們傳遞給 observe.next(value)

冷的 Observables: 在內部建立生產者

如果底層的生產者是在訂閱期間建立並啟用的,那麼 Observable 就是“冷的”。這意味著,如果 Observables 是函式,而生產者是通過呼叫該函式建立並啟用的。

  1. 建立生產者
  2. 啟用生產者
  3. 開始監聽生產者
  4. 單播

下面的示例 Observable 是“冷的”,因為它在訂閱函式(在訂閱該 Observable 時呼叫)中建立並監聽了 WebSocket :

const source = new Observable((observer) => {
  const socket = new WebSocket('ws://someurl');
  socket.addEventListener('message', (e) => observer.next(e));
  return () => socket.close();
});複製程式碼

所以任何 source 的訂閱都會得到自己的 WebSocket 例項,當取消訂閱時,它會關閉 socket 。這意味著 source 是真正的單播,因為生產者只會傳送給一個觀察者。這是用來闡述概念的基礎 JSBin 示例

熱的 Observables: 在外部建立生產者

如果底層的生產者是在 訂閱¹ 外部建立或啟用的,那麼 Observable 就是“熱的”。

  1. 共享生產者的引用
  2. 開始監聽生產者
  3. 多播(通常情況下²)

如果我們沿用上面的示例並將 WebSocket 的建立移至 Observable 的外部,那麼 Observable 就會變成“熱的”:

const socket = new WebSocket('ws://someurl');
const source = new Observable((observer) => {
  socket.addEventListener('message', (e) => observer.next(e));
});複製程式碼

現在任何 source 的訂閱都會共享同一個 WebSocket 例項。它實際上會多播給所有訂閱者。但還有個小問題: 我們不再使用 Observable 來執行拆卸 socket 的邏輯。這意味著像錯誤和完成這樣的通知不再會為我們來關閉 socket ,取消訂閱也一樣。所以我們真正想要的其實是使“冷的” Observable 變成“熱的”。這是用來展示基礎概念的 JSBin 示例

為什麼要變成“熱的” Observable ?

從上面展示冷的 Observable 的第一個示例中,你可以發現所有冷的 Observables 可能都會些問題。就拿一件事來說,如果你不止一次訂閱了 Observable ,而這個 Observable 本身建立一些稀缺的資源,比如 WebSocket 連線,你不想一遍又一遍地建立這個 WebSocket 連線。實際上真的很容易建立了一個 Observable 的多個訂閱而卻沒有意識到。假如說你想要在 WebSocket 訂閱外部過濾所有的“奇數”和“偶數”值。在此場景下最終你會建立兩個訂閱:

source.filter(x => x % 2 === 0)
  .subscribe(x => console.log('even', x));
source.filter(x => x % 2 === 1)
  .subscribe(x => console.log('odd', x));複製程式碼

Rx Subjects

在將“冷的” Observable 變成“熱的”之前,我們需要介紹一個新的型別: Rx Subject 。它有如下特性:

  1. 它是 Observable 。它的結構類似 Observable 並擁有 Observable 的所有操作符。
  2. 它是 Observer 。它作為 Observer 的鴨子型別。當作為 Observable 被訂閱時,將作為 Observer 發出 next 的任何值。
  3. 它是多播的。所有通過 subscribe() 傳遞給它的 Observers 都會被新增到內部的觀察者列表。
  4. 當它完成時,就是完成了。Subjects 在取消訂閱、完成或發生錯誤後無法被複用。
  5. 它通過自身傳遞值。需要重申下第2點。如果 next 值給它,值會從它 observable 那面出來。

Rx 中的 Subject 之所以叫做 “Subject” 是因為上面的第3點。在 GoF (譯註: 大名鼎鼎的《設計模式》一書) 的觀察者模式中,“Subjects” 通常是有 addObserver 的類。在這裡,我們的 addObserver 方法就是 subscribe這是用來展示 Rx Subject 的基礎行為的 JSBin 示例

將冷的 Observable 變成熱的

瞭解了上面的 Rx Subject 後,我們可以使用一些功能性的程式將任何“冷的” Observable 變成“熱的”:

function makeHot(cold) {
  const subject = new Subject();
  cold.subscribe(subject);
  return new Observable((observer) => subject.subscribe(observer));
}複製程式碼

我們的新方法 makeHot 接收任何冷的 Observable 並通過建立由所得到的 Observable 共享的 Subject 將其變成熱的。這是用來演示 JSBin 示例

還有一點問題,就是沒有追蹤源的訂閱,所以當想要拆卸時該如何做呢?我們可以新增一些引用計數來解決這個問題:

function makeHotRefCounted(cold) {
  const subject = new Subject();
  const mainSub = cold.subscribe(subject);
  let refs = 0;
  return new Observable((observer) => {
    refs++;
    let sub = subject.subscribe(observer);
    return () => {
      refs--;
      if (refs === 0) mainSub.unsubscribe();
      sub.unsubscribe();
    };
  });
}複製程式碼

現在我們有了一個熱的 Observable ,當它的所有訂閱結束時,我們用來引用計數的 refs 會變成0,我們將取消冷的源 Observable 的訂閱。這是用來演示的 JSBin 示例

在 RxJS 中, 使用 publish()share()

你可能不應該使用上面提及的任何 makeHot 函式,而是應該使用像 publish()share() 這樣的操作符。將冷的 Observable 變成熱的有很多種方式和手段,在 Rx 中有高效和簡潔的方式來完成此任務。關於 Rx 中可以做此事的各種操作符可以寫上一整篇文章,但這不是文字的目標。本文的目標是鞏固概念,什麼是“熱的”和“冷的” Observable 以及它們的真正意義。

在 RxJS 5 中,操作符 share() 會產生一個熱的,引用計數的 Observable ,它可以在失敗時重試,或在成功時重複執行。因為 Subjects 一旦發生錯誤、完成或取消訂閱,便無法複用,所以 share() 操作符會重複利用已完結的 Subjects,以使結果 Observable 啟用重新訂閱。

這是 JSBin 示例,演示了在 RxJS 5 中使用 share() 將源 Observable 變熱,以及它可以重試

“暖的” Observable

鑑於上述一切,人們能夠看到 Observable 是怎樣的,它只是一個函式,實際上可以同時是“熱的”和“冷的”。或許它觀察了兩個生產者?一個是它建立的而另一個是它複用的?這可能不是個好主意,但有極少數情況下可能是必要的。例如,多路複用的 WebSocket 必須共享 socket ,但同時傳送自己的訂閱並過濾出資料流。

“熱的”和“冷的”都關乎於生產者

如果在 Observable 中複用了生產者的共享引用,它就是“熱的”,如果在 Observable 中建立了新的生產者,它就是“冷的”。如果兩者都做了…。那它到底是什麼?我猜是“暖的”。

註釋

¹ (注意: 生產者在訂閱內部“啟用”,直到未來某個時間點才“建立”出來,這種做法是有些奇怪,但使用代理的話,這也是可能的。) 通常“熱的” Observables 的生產者是在訂閱外部建立和啟用的。

² 熱的 Observables 通常是多播的,但是它們可能正在監聽一個只支援一個監聽器的生產者。在這一點上將其稱之為“多播”有點勉強。

相關文章