web-dev-qa-db-ja.com

rxjs 5 publishReplay refCount

publishReplay().refCount()がどのように機能するかわかりません。

例( https://jsfiddle.net/7o3a45L1/ ):

_var source = Rx.Observable.create(observer =>  {
  console.log("call"); 
  // expensive http request
  observer.next(5);
}).publishReplay().refCount();

subscription1 = source.subscribe({next: (v) => console.log('observerA: ' + v)});
subscription1.unsubscribe();
console.log(""); 

subscription2 = source.subscribe({next: (v) => console.log('observerB: ' + v)});
subscription2.unsubscribe();
console.log(""); 

subscription3 = source.subscribe({next: (v) => console.log('observerC: ' + v)});
subscription3.unsubscribe();
console.log(""); 

subscription4 = source.subscribe({next: (v) => console.log('observerD: ' + v)});
subscription4.unsubscribe();
_

次の結果が得られます。

observerAの呼び出し:5

observerB:5 observerB:5を呼び出す

observerC:5 observerC:5 observerC:5を呼び出す

observerD:5 observerD:5 observerD:5 observerD:5を呼び出す

1)observerB、C、Dが複数回呼び出されるのはなぜですか?

2)「call」が行の先頭ではなく各行に印刷されるのはなぜですか?

また、publishReplay(1).refCount()を呼び出すと、observerB、C、Dをそれぞれ2回呼び出します。

私が期待するのは、すべての新しいオブザーバーが値5を1回だけ受け取り、「呼び出し」が1回だけ出力されることです。

17
Oleg Gello

publishReplay(x).refCount() combinedは次のことを行います。

  • xエミッションまで再生するReplaySubjectを作成します。 xが定義されていない場合、完全なストリームを再生します。
  • このReplaySubjectマルチキャストは、refCount()演算子を使用して互換性があります。これにより、同時サブスクリプションが同じ排出量を受け取ります。

あなたの例には、それがどのように連携するかを曇らせるいくつかの問題が含まれています。次の修正されたスニペットを参照してください。

var state = 5
var realSource = Rx.Observable.create(observer =>  {
  console.log("creating expensive HTTP-based emission"); 
  observer.next(state++);
//  observer.complete();
  
  return () => {
    console.log('unsubscribing from source')
  }
});


var source = Rx.Observable.of('')
  .do(() => console.log('stream subscribed'))
  .ignoreElements()
  .concat(realSource)
.do(null, null, () => console.log('stream completed'))
.publishReplay()
.refCount()
;
    
subscription1 = source.subscribe({next: (v) => console.log('observerA: ' + v)});
subscription1.unsubscribe();
 
subscription2 = source.subscribe(v => console.log('observerB: ' + v));
subscription2.unsubscribe();
    
subscription3 = source.subscribe(v => console.log('observerC: ' + v));
subscription3.unsubscribe();
    
subscription4 = source.subscribe(v => console.log('observerD: ' + v));
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.1.0/Rx.js"></script>

このスニペットを実行すると、Observer Dの重複値を放出していないことが明確にわかります。実際、すべてのサブスクリプションに対して新しい放出を作成しています。どうして?

次のサブスクリプションが行われる前に、すべてのサブスクリプションのサブスクリプションが解除されます。これにより、refCountが実質的にゼロに戻り、マルチキャストは行われません。

問題は、realSourceストリームが完了しないという事実にあります。マルチキャストを行っていないため、次のサブスクライバーはReplaySubjectを介してrealSourceの新しいインスタンスを取得し、新しい排出量には、以前に排出された排出量が付加されます。

そのため、高価なHTTPリクエストを複数回呼び出すことからストリームを修正するには、ストリームを完了して、publishReplayが再サブスクライブする必要がないことを認識する必要があります。

24

通常:refCountは、少なくとも1人のサブスクライバーがいる限りストリームがホット/共有されることを意味しますが、サブスクライバーがいない場合はリセット/コールドされます。

つまり、何も2回以上実行されないことを絶対に確認したい場合は、refCount()を使用せず、単にconnectを使用してストリームをホットに設定する必要があります。

追加の注意として:observer.complete()の後にobserver.next(5);を追加すると、期待した結果も得られます。


サイドノート:本当にここで独自のカスタムObervableを作成する必要がありますか?ケースの95%で、既存の演算子は指定されたユースケースに十分です。

7
olsn

これは、publishReplay()を使用しているために発生します。通過するすべての値を保存するReplaySubjectのインスタンスを内部的に作成します。

単一の値を出力する_Observable.create_を使用しているため、source.subscribe(...)を呼び出すたびに、ReplaySubjectのバッファーに1つの値を追加します。

サブスクライブ時に最初にバッファを発行するのはcallであるため、各行の先頭にReplaySubjectが出力されません。次に、ソースにサブスクライブします。

実装の詳細については、以下を参照してください。

publishReplay(1)を使用する場合も同じです。最初にReplaySubjectからバッファリングされたアイテムを発行し、次にobserver.next(5);からさらに別のアイテムを発行します

4
martin