Aaron Shen Aaron Shen - 3 months ago 16
Javascript Question

When does hot observable emit first value?

I create this pen:
http://codepen.io/hatelove85911/pen/Vjmwwb

const Ob = Rx.Observable
const button = document.querySelector('#click')

const count$ = Ob.fromEvent(button, 'click')
.scan(acc=>++acc,0)
.share()
setTimeout(()=>{
count$.subscribe(x=>console.log('sub1:',x))
},5000)

setTimeout(()=>{
count$.subscribe(x=>console.log('sub2:',x))
},10000)


all online posts say that hot observable starts emit values even when there's no subscriber.

In this example, I've shared the observable to make it hot.
I keep clicking the button for several times, expecting to get a cumulated value bigger than 1 before the first subscriber, but when the first subscriber comes, its log still starts from 1, however, the second subscriber's log will start from in the middle, not from 1.

Can someone explain why this is happening??

Answer

The hot/warm/cold terminology is always going to be confusing. I try to escape the temperature metaphor to understand exactly what's happening under the hood.

So basically, all (Rxjs) observables (with the exception of subjects) are lazy. That means that if there is no subscribers (also termed observers), there will be no data flow (or anything in fact). You will find an illustrated and more precise explanation of the subscription and data flows happening on subscription here : Hot and Cold observables : are there 'hot' and 'cold' operators?

share returns an observable, so that observable is also lazy. The producer (speficied by your operator chaining) will hence start for the first time on the first subscription. So no matter on much you click before subscription, nothing is executed. When you have subscribed once, your producer is executed and your observer/subscription produces the value 1.

As you can see in the linked illustrated data flow, share is an alias for publish().refCount(), where publish is multicast(new Rx.Subject()) so Ob.fromEvent(button, 'click').scan(acc=>++acc,0) has a subject subscribed to it. The important point here is that the subject has actually not subscribed YET, but will be when you will call connect(). Once the subject has subscribed, it will pass on any values it receives to any observers it has registered on him at the moment the value arrives. It is that behaviour that is considered a hot behaviour. The confusing part is that hot observables are observables and hence are still lazy (EXCEPT subjects which are not lazy).

Going into details, publish returns a connectable observable (still lazy). When you subscribe to it, you are subscribing to the aforementioned subject. But as long as you don't do connect(), that subject is not itself subscribed to the source observable. Hence no data will flow.

To convince you of this, replace your codepen with :

const Ob = Rx.Observable
const button = document.querySelector('#click')

const count$ = Ob.fromEvent(button, 'click')
                .scan(acc=>++acc,0)
                .publish();
setTimeout(()=>{
  count$.subscribe(x=>console.log('sub1:',x))
},1000)


setTimeout(()=>{
  count$.subscribe(x=>console.log('sub2:',x))
  count$.connect();
},5000)

So in short in obs.publish(); obs.subscribe(observer1), you have before connection the following state obs | subject -> observer1 where a-->b means b is subscribed to a. No data will flow because the subject only passes on values it receives, and it is not receiving any, being not subscribed to any source.

When you connect() you have the state : obs -> subject -> observer1. Hence obs producer will start, send value to the subject which sends in turn values to any observer it has at the moment of reception of those values.

Comments