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回だけ出力されることです。
publishReplay(x).refCount()
combinedは次のことを行います。
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が再サブスクライブする必要がないことを認識する必要があります。
通常:refCount
は、少なくとも1人のサブスクライバーがいる限りストリームがホット/共有されることを意味しますが、サブスクライバーがいない場合はリセット/コールドされます。
つまり、何も2回以上実行されないことを絶対に確認したい場合は、refCount()
を使用せず、単にconnect
を使用してストリームをホットに設定する必要があります。
追加の注意として:observer.complete()
の後にobserver.next(5);
を追加すると、期待した結果も得られます。
サイドノート:本当にここで独自のカスタムObervable
を作成する必要がありますか?ケースの95%で、既存の演算子は指定されたユースケースに十分です。
これは、publishReplay()
を使用しているために発生します。通過するすべての値を保存するReplaySubject
のインスタンスを内部的に作成します。
単一の値を出力する_Observable.create
_を使用しているため、source.subscribe(...)
を呼び出すたびに、ReplaySubject
のバッファーに1つの値を追加します。
サブスクライブ時に最初にバッファを発行するのはcall
であるため、各行の先頭にReplaySubject
が出力されません。次に、ソースにサブスクライブします。
実装の詳細については、以下を参照してください。
https://github.com/ReactiveX/rxjs/blob/master/src/operator/multicast.ts#L6
https://github.com/ReactiveX/rxjs/blob/master/src/ReplaySubject.ts#L54
publishReplay(1)
を使用する場合も同じです。最初にReplaySubject
からバッファリングされたアイテムを発行し、次にobserver.next(5);
からさらに別のアイテムを発行します