RxJS 系列 – Observable to Subject (Hot, Cold, Warm, connectable, share)

前言

前兩篇介紹了 Observable 和 Subject.它們有一個重大區別當 multiple subscribe 的時候.

Observable 每一次 subscribe 都會調用初始化方法, 並且創建出獨立的一個 stream. 

Subject 則只是把 subscriber 存起來, next 的時候批量調用.

許多人也把 Observable 這種每一次都會創建出新的 source 的行爲稱爲 Cold Observable

而向 Subject 那樣每次 subscribe 不會創建出新 source 的行爲稱爲 Hot Observable

參考: Medium – Hot vs Cold Observables

通常我們比較喜歡 Hot Observable (但不是 100%) 所以 RxJS 有一些方法可以把 Cold 變成 Hot.

這篇主要就是介紹這個.

 

參考

Docs – Multicasting

 

Cold Observable Behaviour

const obs = new Observable(subscriber => {
  console.log('Observable init');

  let index = 0;
  const intervalNumber = setInterval(() => {
    subscriber.next(index++);
    if (index === 5) {
      clearInterval(intervalNumber);
      subscriber.complete();
    }
  }, 1000);

  return () => {
    console.log('Displose Observable');
    clearInterval(intervalNumber);
  };
});

obs.subscribe({
  next: v => console.log('first', v),
  complete: () => console.log('complete'),
});
setTimeout(() => {
  obs.subscribe({
    next: v => console.log('second', v),
    complete: () => console.log('complete'),
  });
}, 2000);

有一個 Observable, 被 subscribe 2 次

效果

它會創建出 2 個獨立的 interval, 相互不影響.

 

Cold to Hot 原理

如果我們希望它只創建一次 interval, 2 個 subscribe 訂閱同一個 source 可以嗎?

可以, 它的底層實現原理就搞一箇中間人 Subject.

Subject 可以 multiple subscribe 同一個 source.

這樣 Observable 只被 subscribe 一次所以只會有一個 interval, 而 Subject 可以被 subscribe 多次.

 

RxJS 6.0 和 7.0 的區別

RxJS 6.0 和 7.0 Cold to Hot 的方法差距甚遠. 這裏只介紹 7.0 的方案, 如果想考古可以參考

RxJS Multicast 類 Operator (1) - multicast / publish / refCount / share / shareReplay

30 天精通 RxJS(24): Observable operators - multicast, refCount, publish, share

The magic of RXJS sharing operators and their differences

 

connectable

create connectable. connect and disconnect

RxJS 提供了一些方法, 方便我們實現 Cold to Hot, 原理就像上面說的.

const con = connectable(obs, {
  connector: () => {
    console.log('create connector');
    return new Subject();
  },
  resetOnDisconnect: false,
});
const subscription = con.connect();
subscription.unsubscribe();
con.connect();

connectable 會返回一個 Connectable 對象. 當調用 .connect() 的時候. 

observable 被訂閱. 當 .unsubscribe 的時候, obserable 被退訂 (displose)

resetOnDisconnect 是聲明當 re-connect (unsubscribe 之後又 connect) 是否創建新的 Subject 還是複用之前的, 默認是 true.

像上面這個例子, 當第二次調用 .connect 時, 'create connector' 不會觸發 (它只會觸發一次), 而如果 resetOnDisconnect: true 那麼它就會觸發 2 次.

爲什麼要搞 reset 呢? 因爲 Subject 有可能 completed, 是否要保留之前的記入要依據項目需求. 

subscribe connectable

con.subscribe({
  next: v => console.log('first', v),
  complete: () => console.log('complete'),
});
setTimeout(() => {
  con.subscribe({
    next: v => console.log('second', v),
    complete: () => console.log('complete'),
  });
}, 2000);

接着其它的 subscirbe 都 apply 到 connectable 上, 它就是中間人.

效果

2 個 subscribe 都 apply 到了同一個 source 上.

 

Cold to Hold 要注意的事項

Observable 的特色是, subscribe 時才初始化, unsubscribe 以後 displose.

但一旦 Subject 介入以後, 什麼時候初始化, 什麼時候 displose 就變成一個要思考的問題了.

大部分情況, 我們會認爲, 當第一個 subscribe 調用時, Observable 才被初始化. (不是 100% 場景都這樣啦)

上面 connectable 的例子中, 我是一早就調用了 connect, 這樣 Observable 立馬就初始化了. 而不是等到第一次被 subscribe. 所以不符合現在的要求.

另外, 大部分情況下, 我們會認爲, 當所有的 subscriber unsubscribe 以後, Observable 被 displose (不是 100% 場景都這樣啦)

上面 connectable 的例子中, Observable 最終會 complete, 所以它不是通過 unsubscribe 形成 displose 的.

雖然例子沒有符合大部分的場景, 但不要緊, connectable 的接口足夠底層, 我們完全可以控制什麼時候要 connect 什麼時候要 disconnect (displose)

但由於大部分場景真的就是那樣, 所以 RxJS 封裝了一個 share 方法, 讓我們更容易去實現這種 connect 和 disconnect 的邏輯. 

 

Share

share 和 connectable 差不多, 只是它不需要我們去管理 connect 和 disconnect.

const shareableObs = obs.pipe(
  share({
    connector: () => {
      console.log('create connector');
      return new Subject();
    },
    resetOnComplete: true,
    resetOnError: true,
    resetOnRefCountZero: true,
  })
);

它的規則是這樣的, 當第一個 subscribe 調用後, Observable 被訂閱. 接着的 subscribe 不會再初始胡 Observable

當 Observable complete 或者 error 以後. 如果有新的 subscriber, 那麼 Observable 會被重新初始胡. 

這就是 resetOnComplete 和 resetOnError 的意思. 如果 false 就表示不會重新初始化, 那麼這個 subscriber 會直接收到 complete 或者 error.

resetOnRefCountZero 指的是, 當所有 subscriber unsubscribe 以後 (ref count = zero), 是否要退訂 Observable. true 表示要. 那麼 Observable 就 displose 了

當下一個 subscribe 來後, Observable 重新初始化. 而 false 表示不要退訂, 那麼 Observable 只能靠自己 complete 或 error 纔會 displose.

題外話, 過往的經驗

以前寫 Angular 的時候, 遇過這樣一個情況, component 和 view 分別需要 subscribe 一個 stream. 

component 只要 take 1, 然後我配上 await toPromise. 這樣 ref count 就變成 0 了. 當 view 渲染的時候 Observable 又初始化了.

可如果設置 resetOnRefCountZero: false 又可能導致 Observable 無法 displose. 

目前看解決思路有 2 個.

1. 搞一個假的 subscriber hacking 它. 讓它的 ref count 不會變成 0 直到 component destroy.

2. 使用 connectable 自己管理 connect 和 disconnect.

 

ShareReplay

它底層調用的是 share, 只是用了 ReplaySubject. 這樣可以 cache value.

 

總結

Observable 遇到 multiple subscribe, 會創建出多個獨立的 stream. 但有時我們是想共享 stream 的.

這時就可以用 connectable 或者 share 來達到目的.

其原理是在中間加了一層 Subject. 由 Subject 來 subscribe Observable, 其它人 subscribe Subject.

此外我們需要注意 Observable 的初始化和 displose 的時機.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章