2

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??

user3743222
  • 18,345
  • 5
  • 69
  • 75
Aaron Shen
  • 8,124
  • 10
  • 44
  • 86
  • I´m not very familiarize with rxjs, but where are you specifying that is a hot and not cold observable? – paul Jun 22 '16 at 08:02
  • I think, by default, button click should be hot, right? because regardless whether there's subscriber, the button is still be clicked. I used the method `.share()` to ensure it's hot. – Aaron Shen Jun 22 '16 at 08:17

2 Answers2

4

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.

Community
  • 1
  • 1
user3743222
  • 18,345
  • 5
  • 69
  • 75
  • Thanks a lot for your answer. Now I know to understand all of this, I need to understand the implementation behind. Hot and cold are really confusing, or it's confusing because no good material explains the data flow, subscription in detail. – Aaron Shen Jun 23 '16 at 02:42
  • Can I confirm these: 1. hot observable means under the hood, they all use subject as a intermediator to multicast values from source to subscribers. 2. not matter observable hot or cold, they'll only emit values when there's at least one subscriber. 3. a lot of online posts are wrong, they say hot observable will emit values regardless whether there's subscriber or not, hot observable will only emit values when there's at least one subscriber. – Aaron Shen Jun 24 '16 at 22:25
  • Well if by observable we mean the javascript objects defined in Rxjs, then yes to 1. One caveat for 2 - subjects will emit even when there is no subscriber (i.e. they are not lazy) and you always have to mention them because they are observables too (as well as observers). What happens is sometimes people say hot observable when referring not to the javascript object but to the source itself (say, the sequence of clicks vs. the javascript object which represents this sequence : the object is a lazy hot observable while the sequence of clicks is happening whether you are listening or not). – user3743222 Jun 24 '16 at 22:50
1

I'm new to RxJS as well, but I believe the fromEvent is more like a "warm" Observable. Basically, it's lazy in that it wont start tracking things until an actual subscription happens on it. share does make it hot, once a subscription has happened but nothing is tracked until the first subscription due to its laziness, so it's still more of a "warm" Observable in that sense.

To make it truly hot/not-lazy, in that, clicks are tracked before the first subscription you can use .publish() (which returns a ConnectableObservables) and then immediately .connect() it.

Codepen.io Example

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

const count$ = Ob.fromEvent(button, 'click')
                .scan(acc=>++acc,0)
                .publish(); // <-- publish here

count$.connect(); // <--immediately connect to it

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


setTimeout(()=>{
  count$.subscribe(x=>console.log('sub2:',x))
},10000)
subhaze
  • 8,815
  • 2
  • 30
  • 33