Je ne peux pas comprendre comment publishReplay().refCount()
fonctionne.
Par exemple ( 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();
donne le résultat suivant:
appeler observateurA: 5
observerB: 5 appeler observateurB: 5
observerC: 5 observerC: 5 appeler observateurC: 5
observateurD: 5 observateurD: 5 observateurD: 5 appel observateurD: 5
1) Pourquoi les observateurs B, C et D sont-ils appelés plusieurs fois?
2) Pourquoi "appel" est imprimé sur chaque ligne et non au début de la ligne?
De plus, si j'appelle publishReplay(1).refCount()
, il appelle observateurB, C et D 2 fois chacun.
Ce que j'attends, c'est que chaque nouvel observateur reçoive la valeur 5 exactement une fois et "call" ne soit imprimé qu'une seule fois.
publishReplay(x).refCount()
combiné fait ce qui suit:
ReplaySubject
qui rejoue jusqu'à x émissions. Si x n'est pas défini, il rejoue le flux complet.ReplaySubject
compatible multidiffusion en utilisant un opérateur refCount (). Cela se traduit par simultanés abonnements recevant les mêmes émissions.Votre exemple contient quelques problèmes concernant la façon dont tout cela fonctionne ensemble. Voir l'extrait de code révisé suivant:
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>
En exécutant cet extrait, nous pouvons voir clairement qu'il n'émet pas de valeurs en double pour Observer D, il crée en fait de nouvelles émissions pour chaque abonnement. Comment venir?
Chaque abonnement est désabonné avant le prochain abonnement. Cela ramène effectivement le refCount à zéro, aucune multidiffusion n'est en cours.
Le problème réside dans le fait que le flux realSource
ne se termine pas. Étant donné que nous ne diffusons pas en multidiffusion, le prochain abonné obtient une nouvelle instance de realSource
via ReplaySubject et les nouvelles émissions sont ajoutées aux émissions précédentes déjà émises.
Ainsi, pour empêcher votre flux d'invoquer plusieurs fois la coûteuse requête HTTP, vous devez terminer le flux afin que le publishReplay sache qu'il n'a pas besoin de se réinscrire.
Généralement: refCount
signifie que le flux est chaud/partagé tant qu'il y a au moins 1 abonné - cependant, il est réinitialisé/froid lorsqu'il n'y a pas d'abonnés.
Cela signifie que si vous voulez être absolument sûr que rien n'est exécuté plus d'une fois, vous ne devez pas utiliser refCount()
mais simplement connect
le flux pour le mettre à chaud.
Remarque supplémentaire: si vous ajoutez une observer.complete()
après la observer.next(5);
, vous obtiendrez également le résultat attendu.
Sidenote: Avez-vous vraiment besoin de créer votre propre Obervable
ici? Dans 95% des cas, les opérateurs existants sont suffisants pour le cas d'utilisation donné.
Cela se produit car vous utilisez publishReplay()
. Il crée en interne une instance de ReplaySubject
qui stocke toutes les valeurs qui passent.
Puisque vous utilisez Observable.create
Où vous émettez une seule valeur, chaque fois que vous appelez source.subscribe(...)
, vous ajoutez une valeur au tampon dans ReplaySubject
.
Vous n'obtenez pas call
imprimé au début de chaque ligne parce que c'est le ReplaySubject
qui émet son tampon en premier lorsque vous vous abonnez, puis il s'abonne à sa source:
Pour les détails d'implémentation, voir:
https://github.com/ReactiveX/rxjs/blob/master/src/operator/multicast.ts#L6
https://github.com/ReactiveX/rxjs/blob/master/src/ReplaySubject.ts#L54
La même chose s'applique lors de l'utilisation de publishReplay(1)
. Tout d'abord, il émet l'élément mis en mémoire tampon depuis ReplaySubject
puis encore un autre élément depuis observer.next(5);