web-dev-qa-db-fra.com

Observables chauds et froids: existe-t-il des opérateurs «chauds» et «froids»?

J'ai passé en revue la SO question: Quels sont les observables chauds et froids?

Résumer:

  • un observable froid émet ses valeurs lorsqu'il a un observateur pour les consommer, c'est-à-dire que la séquence de valeurs reçues par les observateurs est indépendante du moment de la souscription. Tous les observateurs consommeront la même séquence de valeurs.
  • un observable à chaud émet de la valeur indépendamment de ses abonnements, c'est-à-dire que les valeurs reçues par les observateurs sont fonction du moment de l'abonnement.

Pourtant, j'ai l'impression que le chaud contre le froid est toujours une source de confusion. Donc, voici mes questions:

  • Tous les rx observables sont-ils froids par défaut (à l'exception des sujets)?

    Je lis souvent que les événements sont la métaphore typique des observables chauds, mais je lis aussi que Rx.fromEvent(input, 'click') est un observable froid (?).

  • Y a-t-il/quels sont les opérateurs Rx qui transforment un observable froid en observable chaud (à part publish et share)?

    Par exemple, comment cela fonctionne-t-il avec l'opérateur Rx withLatestFrom? Soit cold$ Un observable froid auquel quelque part a été souscrit. sth$.withLatestFrom(cold$,...) sera-t-il un observable à chaud?

    Ou si je fais sth1$.withLatestFrom(cold$,...), sth2$.withLatestFrom(cold$,...) et m'abonne à sth1 Et sth2, Verrai-je toujours la même valeur pour les deux sth?

  • Je pensais que Rx.fromEvent Crée des observables froids mais ce n'est pas le cas, comme mentionné dans l'une des réponses. Cependant, je suis toujours déconcerté par ce comportement: codepen.io/anon/pen/NqQMJR?editors=101 . Différents abonnements obtiennent des valeurs différentes du même observable. L'événement click n'a-t-il pas été partagé?

58
user3743222

Je reviens quelques mois plus tard à ma question initiale et souhaitais partager entre-temps les connaissances acquises. J'utiliserai le code suivant comme support d'explication ( jsfiddle ):

var ta_count = document.getElementById('ta_count');
var ta_result = document.getElementById('ta_result');
var threshold = 3;

function emits ( who, who_ ) {return function ( x ) {
  who.innerHTML = [who.innerHTML, who_ + " emits " + JSON.stringify(x)].join("\n");
};}

var messages$ = Rx.Observable.create(function (observer){
  var count= 0;
  setInterval(function(){
    observer.onNext(++count);
  }, 1000)
})
.do(emits(ta_count, 'count'))
.map(function(count){return count < threshold})
.do(emits(ta_result, 'result'))

messages$.subscribe(function(){});

Comme mentionné dans l'une des réponses, la définition d'un observable conduit à une série de rappel et d'enregistrement de paramètres. Le flux de données doit être lancé, et cela se fait via la fonction subscribe. Un flux détaillé (simplifié pour illustration) peut être trouvé par la suite.

Simplified flow diagram

Les observables sont froids par défaut. La souscription à un observable se traduira par une chaîne d'abonnements en amont. Le dernier abonnement conduit à l'exécution d'une fonction qui va gérer une source et émettre ses données à son observateur.

Cet observateur émet à son tour vers l'observateur suivant, ce qui entraîne un flux de données en aval, vers l'observateur du puits. L'illustration simplifiée suivante montre l'abonnement et les flux de données lorsque deux abonnés s'abonnent au même observable.

Cold observable simplified flow diagram

Les observables à chaud peuvent être créés soit en utilisant un sujet, soit via l'opérateur multicast (et ses dérivés, voir la note 3 ci-dessous).

L'opérateur multicast sous le capot utilise un sujet et renvoie un observable connectable. Tous les abonnements à l'opérateur seront des abonnements au sujet intérieur. Lorsque connect est appelé, le sujet interne s'abonne à l'observable en amont et les données circulent en aval. Les sujets manipulent en interne une liste d'observateurs abonnés et multidiffusent les données entrantes vers tous les observateurs abonnés.

Le diagramme suivant résume la situation.

Hot observable simplified flow diagram

En fin de compte, il importe davantage de comprendre le flux de données provoqué par le modèle d'observation et la mise en œuvre des opérateurs.

Par exemple, si obs est chaud, est hotOrCold = obs.op1 froid ou chaud? Quelle que soit la réponse:

  • s'il n'y a pas d'abonnés à obs.op1, aucune donnée ne transite par op1. S'il y avait des abonnés à hot obs, cela signifie obs.op1 aura probablement perdu des données
  • en supposant que op1 n'est pas un opérateur de type multidiffusion, s'abonner deux fois à hotOrCold s'abonnera deux fois à op1, et chaque valeur de obs circulera deux fois dans op1.

Remarques :

  1. Ces informations doivent être valides pour Rxjs v4. Bien que la version 5 ait subi des changements considérables, la plupart s'appliquent toujours textuellement.
  2. Les flux de désabonnement, d'erreur et d'achèvement ne sont pas représentés, car ils ne relèvent pas de la portée de la question. Les planificateurs ne sont pas non plus pris en compte. Entre autres, ils influencent le timing du flux de données, mais a priori pas sa direction et son contenu.
  3. Selon le type de sujet utilisé pour la multidiffusion, il existe différents opérateurs de multidiffusion dérivés:

Subject type | `Publish` Operator | `Share` operator ------------------ | --------------------------- | ----------------- Rx.Subject | Rx.Observable.publish | share Rx.BehaviorSubject | Rx.Observable.publishValue | shareValue Rx.AsyncSubject | Rx.Observable.publishLast | N/A Rx.ReplaySubject | Rx.Observable.replay | shareReplay

Mise à jour : Voir aussi les articles suivants, ici, et là ) à ce sujet par Ben Lesh.

D'autres détails sur les sujets peuvent être trouvés dans cette autre SO question: Quelle est la sémantique des différents sujets RxJS?

71
user3743222

Votre résumé et la question liée sont tous deux corrects, je pense que la terminologie peut vous prêter à confusion. Je vous propose de considérer les observables chauds et froids comme des observables actifs et passifs (respectivement).

Autrement dit, un observable actif (à chaud) émettra des éléments, que quelqu'un se soit abonné ou non. L'exemple canonique, encore une fois, les événements de clic de bouton se produisent, que quelqu'un les écoute ou non. Cette distinction est importante car, si, par exemple, je clique sur un bouton, puis je m'abonne aux clics sur les boutons (dans cet ordre), je ne verrai pas le clic sur le bouton qui s'est déjà produit.

Un observable passif (froid) attendra qu'un abonné existe avant d'émettre des éléments. Imaginez un bouton sur lequel vous ne pouvez pas cliquer dessus jusqu'à ce que quelqu'un écoute les événements - cela garantirait que vous voyez toujours chaque événement de clic.

Tous les observables Rx sont-ils "froids" (ou passifs) par défaut? Non, Rx.fromEvent(input, 'click') par exemple est un observable à chaud (ou actif).

J'ai également lu que Rx.fromEvent(input, 'click') est un observable à froid (?)

Ce n'est pas le cas.

Y a-t-il des opérateurs Rx qui transforment un observable froid en observable chaud?

Le concept de transformer un observable chaud (actif) en observable froid (passif) est le suivant: vous devez enregistrer les événements qui se produisent alors que rien n'est souscrit et proposer ces éléments (de diverses manières) aux abonnés qui viendront à l'avenir. Une façon de le faire est d'utiliser un Subject . Par exemple, vous pouvez utiliser un ReplaySubject pour mettre en mémoire tampon les éléments émis et les relire aux futurs abonnés.

Les deux opérateurs que vous avez nommés (publish et share) utilisent tous deux des sujets en interne pour offrir cette fonctionnalité.

Comment ça marche avec l'opérateur Rx withLatestFrom? Soit cold$ Un observable froid auquel vous avez souscrit. something$.withLatestFrom(cold$,...) sera-t-il un observable à chaud?

Si something est un observable à chaud, alors oui. Si something est observable à froid, alors non. Pour revenir à l'exemple d'événements, si something est un flux d'événements de clic de bouton:

var clickWith3 = Rx.fromEvent(input, 'click')
    .withLatest(Rx.Observable.from([1, 2, 3]);

Ou si je fais foo$.withLatestFrom(cold$,...), bar$.withLatestFrom(cold$,...) et que je m'abonne à foo et bar, verrai-je toujours les mêmes valeurs pour les deux?

Pas toujours. Encore une fois, si foo et bar sont des clics sur différents boutons par exemple, alors vous verrez des valeurs différentes. De même, même s'il s'agissait du même bouton, si votre fonction de combinaison (le deuxième argument de withLatest) ne renvoie pas le même résultat pour les mêmes entrées, vous ne verrez pas les mêmes valeurs (car cela être appelé deux fois, comme expliqué ci-dessous).

Je pensais que Rx.fromEvent Crée des observables froids mais ce n'est pas le cas, comme mentionné dans l'une des réponses. Cependant, je suis toujours déconcerté par ce comportement: codepen.io/anon/pen/NqQMJR?editors=101 . Différents abonnements obtiennent des valeurs différentes du même observable. L'événement click n'a-t-il pas été partagé?

Je vais vous indiquer cette excellente réponse d'Enigmativity à une question que j'avais sur le même comportement. Cette réponse l'expliquera beaucoup mieux que moi, mais l'essentiel est que la source (l'événement click) est "partagée", oui, mais vos opérations dessus ne le sont pas. Si vous souhaitez partager non seulement l'événement de clic, mais également l'opération sur celui-ci, vous devrez le faire explicitement.

8
Whymarrh

values dans votre codepen est paresseux - rien ne se passe jusqu'à ce que quelque chose s'abonne, à quel point il passe et le connecte. Ainsi, dans votre exemple, bien que vous vous abonniez à la même variable, cela crée deux flux différents; un pour chaque appel d'abonnement.

Vous pouvez considérer values comme un générateur de flux pour click avec ce map attaché.

.share() à la fin de cette carte créerait le comportement que nous attendons, car il est implicitement souscrit.

4
electrichead

Ce n'est pas une réponse à toutes vos questions (j'aimerais toutes les connaître!) Mais pour sûr, tous les fromEvent observables sont chauds. Le clic semble ne pas l'être car ce n'est pas un événement "continu" comme mousemove, mais de toute façon l'abonnement à la source (appel addEventListener ou on) n'est effectué qu'une seule fois, lorsque Observable est créé. Il fait donc chaud. Vous pouvez le voir dans le code source de l'opérateur ici et - l'observable créé est shared quel que soit le nom ou la source de l'événement.

3
Eryk Napierała