web-dev-qa-db-fra.com

rxjs 5 publishReplay refCount

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.

17
Oleg Gello

publishReplay(x).refCount() combiné fait ce qui suit:

  • Il crée un ReplaySubject qui rejoue jusqu'à x émissions. Si x n'est pas défini, il rejoue le flux complet.
  • Il rend cette 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.

24
Mark van Straten

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é.

7
olsn

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:

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);

4
martin