Observables fríos y calientes: ¿hay operadores "fríos" y "calientes"?
Revisé la siguiente pregunta SO: ¿Cuáles son los observables fríos y calientes?
Para resumir:
- un observable frío emite sus valores cuando tiene un observador que los consume, es decir, la secuencia de valores recibidos por los observadores es independiente del tiempo de suscripción. Todos los observadores consumirán la misma secuencia de valores.
- un observable caliente emite valor independientemente de sus suscripciones, es decir, los valores recibidos por los observadores son función del tiempo de suscripción.
Sin embargo, siento que el calor versus el frío sigue siendo una fuente de confusión. Asi que aqui están mis preguntas:
¿Todos los observables de rx están fríos de forma predeterminada (con la excepción de los sujetos)?
A menudo leo que los eventos son la metáfora típica de los observables calientes, pero también leo que
Rx.fromEvent(input, 'click')
son observables fríos (?).¿Existen/cuáles son los operadores Rx que convierten un observable frío en un observable caliente (aparte de
publish
yshare
)?Por ejemplo, ¿cómo funciona con el operador Rx
withLatestFrom
? Seacold$
un observable frío al que se haya suscrito en algún lugar. ¿ Serásth$.withLatestFrom(cold$,...)
un observable caliente?O si lo hago
sth1$.withLatestFrom(cold$,...), sth2$.withLatestFrom(cold$,...)
y me suscribo asth1
ysth2
, ¿veré siempre el mismo valor para ambossth
?Pensé que
Rx.fromEvent
crea observables fríos, pero ese no es el caso, como se menciona en una de las respuestas. Sin embargo, todavía estoy desconcertado por este comportamiento: https://codepen.io/anon/pen/NqQMJR?editors=101 . Diferentes suscripciones obtienen valores diferentes del mismo observable. ¿ No seclick
compartió el evento?
Vuelvo unos meses más tarde a mi pregunta original y mientras tanto quería compartir los conocimientos adquiridos. Usaré el siguiente código como soporte explicativo ( 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(){});
Como se menciona en una de las respuestas, definir un observable conduce a una serie de devoluciones de llamada y registro de parámetros. El flujo de datos debe iniciarse, y eso se hace a través de la subscribe
función. A continuación se puede encontrar un flujo detallado (simplificado a modo de ilustración).
Los observables son fríos por defecto. La suscripción a un observable dará como resultado una cadena ascendente de suscripciones. La última suscripción conduce a la ejecución de una función que manejará una fuente y emitirá sus datos a su observador.
Ese observador, a su vez, emite al siguiente observador, lo que da como resultado un flujo de datos descendente, hasta el observador sumidero. La siguiente ilustración simplificada muestra los flujos de datos y suscripción cuando dos suscriptores se suscriben al mismo observable.
Los observables calientes se pueden crear utilizando un sujeto o a través del multicast
operador (y sus derivados, consulte la Nota 3 a continuación).
El multicast
operador oculto utiliza un sujeto y devuelve un observable conectable. Todas las suscripciones al operador serán suscripciones al sujeto interno. Cuando connect
se llama, el sujeto interno se suscribe al observable ascendente y los datos fluyen descendentes. Los sujetos manipulan internamente una lista de observadores suscritos y transmiten datos entrantes a todos los observadores suscritos.
El siguiente diagrama resume la situación.
Al final, es más importante comprender el flujo de datos causado por el patrón del observador y la implementación de los operadores.
Por ejemplo, si obs
hace calor, ¿hace hotOrCold = obs.op1
frío o calor? Cualquiera que sea la respuesta:
- si no hay suscriptores
obs.op1
, no fluirá ningún datoop1
. Si hubiera suscriptores de hotobs
, eso significa queobs.op1
posiblemente se habrán perdido datos - Suponiendo que
op1
no sea un operador de tipo multidifusión, suscribirse dos veces ahotOrCold
se suscribirá dos veces aop1
y cada valor deobs
fluirá dos veces a través deop1
.
Notas:
- Esta información debería ser válida para Rxjs v4. Si bien la versión 5 ha pasado por cambios considerables, la mayor parte todavía se aplica palabra por palabra.
- Los flujos de cancelación de suscripción, errores y finalización no están representados, ya que no están dentro del alcance de la pregunta. Los programadores tampoco se tienen en cuenta. Entre otras cosas, influyen en el momento del flujo de datos, pero a priori no en su dirección y contenido.
- Según el tipo de tema utilizado para la multidifusión, existen diferentes operadores de multidifusión derivados:
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
Actualización : consulte también los siguientes artículos, aquí y allá ) sobre ese tema de Ben Lesh.
Se pueden encontrar más detalles sobre los temas en esta otra pregunta SO: ¿ Cuáles son la semántica de los diferentes temas de RxJS?
Su resumen y la pregunta vinculada son correctos, creo que la terminología puede resultar confusa. Le propongo que piense en los observables fríos y calientes como observables activos y pasivos (respectivamente).
Es decir, un observable activo (caliente) emitirá elementos independientemente de que alguien se haya suscrito o no. En el ejemplo canónico, nuevamente, los eventos de clic en un botón ocurren ya sea que alguien los esté escuchando o no. Esta distinción es importante porque, si, por ejemplo, hago clic en un botón y luego me suscribo a los clics en el botón (en ese orden), no veré el clic en el botón que ya ocurrió.
Un observable pasivo (frío) esperará hasta que exista un suscriptor antes de emitir elementos. Imagine un botón en el que no puede hacer clic en él hasta que alguien esté escuchando los eventos; esto garantizaría que siempre vea todos y cada uno de los eventos de clic.
¿Todos los observables de Rx son "fríos" (o pasivos) de forma predeterminada? No, Rx.fromEvent(input, 'click')
por ejemplo, es un observable caliente (o activo).
También leí que
Rx.fromEvent(input, 'click')
es un frío observable (?)
Ese no es el caso.
¿Existen operadores de Rx que convierten un observable frío en un observable caliente?
El concepto de convertir un observable caliente (activo) en un observable frío (pasivo) es el siguiente: es necesario registrar los eventos que suceden mientras no hay nada suscrito y ofrecer esos elementos (de varias maneras) a los suscriptores que lleguen en el futuro. Una forma de hacerlo es utilizar un Asunto . Por ejemplo, podría utilizar a ReplaySubject
para almacenar en búfer los elementos emitidos y reproducirlos para futuros suscriptores.
Los dos operadores que nombró ( publish
y share
) utilizan sujetos internamente para ofrecer esa funcionalidad.
¿Cómo funciona con el operador Rx
withLatestFrom
? Seacold$
un observable frío al que se ha suscrito. ¿ Serásomething$.withLatestFrom(cold$,...)
un observable caliente?
Si something
es un observable candente, entonces sí. Si something
se observa un resfriado, entonces no. Volviendo al ejemplo de eventos, si something
es una secuencia de eventos de clic de botón:
var clickWith3 = Rx.fromEvent(input, 'click')
.withLatest(Rx.Observable.from([1, 2, 3]);
O si lo hago
foo$.withLatestFrom(cold$,...), bar$.withLatestFrom(cold$,...)
y me suscribo afoo
ybar
, ¿veré siempre los mismos valores para ambos?
No siempre. Nuevamente, si foo
y bar
se hacen clic en botones diferentes, por ejemplo, verá valores diferentes. Además, incluso si fueran el mismo botón, si su función de combinación (el segundo argumento de withLatest
) no devuelve el mismo resultado para las mismas entradas, entonces no vería los mismos valores (porque se llamaría dos veces, como se explicó). abajo).
Pensé que
Rx.fromEvent
crea observables fríos, pero ese no es el caso, como se menciona en una de las respuestas. Sin embargo, todavía estoy desconcertado por este comportamiento: codepen.io/anon/pen/NqQMJR?editors=101 . Diferentes suscripciones obtienen valores diferentes del mismo observable. ¿ No seclick
compartió el evento?
Les señalaré esta gran respuesta de Enigmativity a una pregunta que tenía sobre el mismo comportamiento. Esa respuesta lo explicará mucho mejor que yo, pero lo esencial es que la fuente (el evento de clic) está "compartida", sí, pero sus operaciones en ella no. Si desea compartir no solo el evento de clic sino también la operación realizada en él, deberá hacerlo explícitamente.
values
en su codepen es vago: no sucede nada hasta que algo se suscribe, momento en el que lo ejecuta y lo conecta. Entonces, en su ejemplo, aunque se suscribe a la misma variable, se crean dos flujos diferentes; uno para cada llamada de suscripción.
Puedes considerarlo values
como un generador de transmisiones con click
eso map
adjunto.
.share()
al final de ese mapa crearía el comportamiento que esperamos, porque está implícitamente suscribiéndose.