[譯] 熱的 Vs 冷的 Observables

原文鏈接: https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339
本文為 RxJS 中文社區 翻譯文章,如需轉載,請注明出處,謝謝合作!
如果你也想和我們一起,翻譯更多優質的 RxJS 文章以奉獻給大家,請點擊【這里】

Hot-Vs-Cold.png

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: 在外部創建生產者

如果底層的生產者是在 訂閱1 外部創建或激活的,那么 Observable 就是“熱的”。

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

如果我們沿用上面的示例并將 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 中創建了新的生產者,它就是“冷的”。如果兩者都做了…。那它到底是什么?我猜是“暖的”。

注釋

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容