From RxJS in Action by Paul P. Daniels and Luis Atencio

In this article, we’ll take close look at hot and cold observables, how they differ, the benefits of each, and how you can take advantage of this in your code.


Save 37% on RxJS in Action. Just enter code fccdaniels into the discount code box at checkout at manning.com.


An observable function is a lazy computation, which means the entire sequence of operators that encompass the observable declaration won’t begin executing until an observer subscribes to it. Have you ever thought about what happens to the events that occur before the subscription happens? For instance, what does a mouse move observable do with the mouse events, or a WebSocket that has received a set of messages? Do these potentially crucial occurrences get lost in the æther? The reality is that these active data sources won’t wait for subscribers to listen before they begin emitting events, and it’s important for you to know how to deal with this situation. Observables come in two different flavors: hot and cold. This isn’t a simple topic to grasp; it’s probably one of the most complex in the RxJS world, which is why we wanted to dedicate an article to it.

Let’s look into sharing a single observable sequence with multiple observers, rather than combining multiple streams funneled through one observer. For instance, we can take a simple numerical stream and share its values with multiple subscribers, or take a single web socket message and broadcast it to multiple subscribers. This is different from the single stream subscriber cases normally dealt with. First, let’s begin by understanding the differences between hot and cold observables.


Introducing hot and cold observables

Think about when you turn on your TV set and switch to your favorite show. If you catch the show ten minutes after it began, will it start from the beginning? Restarting the show would mean that cable companies would have to broadcast independent streams to every subscriber—which would be great, if impractical for cable companies. The reality is that the same content is broadcast out to all subscribers. Unless you have a recording device, which we can associate with a buffer operator, you can’t replay content that happened in the past.

A TV’s livestream is equivalent to a hot observable. Like we already mentioned, RxJS divides observable sources into one of two categories, hot or cold. These categories determine the behavioral characteristics not only of subscription semantics, but also of the entire lifetime of the stream. An observable’s temperature also affects whether the stream and the producer are managed together or separately, which can greatly affect resource utilization (we’ll get to this shortly). We classify an observable as either hot or cold based on the nature of the data source being observed. Let’s begin with the cold observables.

Cold Observables

In simple terms, a cold observable is one that doesn’t begin emitting its values until an observer subscribes. Cold observables are typically used to wrap bounded data types, such as numbers, ranges of numbers, strings, arrays, and HTTP requests, as well as unbounded types like generator functions. These resources are known as passive, in the sense that their declaration is independent of their execution. This also means that these observables are truly lazy in their creation and execution.

Being cold means that each new subscription is creating a new independent stream with a new “starting” point for that stream. This means that subscribers independently receive the same set of events, always, from the beginning. Another way to conceptualize it: when creating a cold observable, you’re creating a plan or recipe to be executed later—repeatedly, from top to bottom. The recipe itself is a set of instructions (operators) that tell the JavaScript runtime engine how to combine and cook the ingredients (data); cold observables begin emitting events only when we start “cooking”.

Pure Observables Observables are pure when they abide by the functional programming principles of a pure function, which is immutable, side-effect free, and repeatable. To support a desirable functional property known as referential transparency, functions must be repeatable and predictable, which means that invoking a function with the same arguments always yields the same result. The same holds for cold observables when viewed as functions that produce (return) a set of values.

From a pure functional programming perspective, we think of cold observables as behaving like functions. A function can be reasoned a lazy or to-be-computed value which is returned when you invoke it, only when needed (languages with lazy evaluation, like Haskell, work this way). Similarly, observable objects won’t run until subscribed to, and you can use the provided observers to process their return values. You can visualize this resemblance in figure 1:


Figure 1 A cold observable can be thought of as a function that takes input, or the data to be processed, and based on this returns an output to the caller


Furthermore, the declaration of a cold observable frequently begins with static operators such as of() or from(), and timing observables interval() and timer() behave “coldly”. Here’s a quick example:

    const arr$ = Rx.Observable.from([1,2,3,4,5,6,7,8,9]);   const sub1 = arr$.subscribe(console.log);  ❶   // ... moments later ... // const sub2 = arr$.subscribe(console.log);  ❷    

❶  Every subscriber gets their own independent copy of the same data no matter when the subscription happens

❷   sub2 could’ve subscribed moments later, yet it still receives the entire array of elements.

 

With cold observables, all subscribers, no matter what point in time the subscription occurred, will observe the same events. Another example is the interval() operator. Each time a new subscription occurs a brand-new interval instance is created like in figure 2:


Figure 2 A cold observable is like an object factory, which can be used to create a family of subscriptions that receive their own independent copy of all events pushed through


Interval acts like a factory for timers, where each timer operates on its own schedule and each subscription can be independently created and cancelled, as needed. Cold observables can be likened to a factory function that produces stream instances based on a template (its pipeline) for each subscriber to consume fully. Listing 1 demonstrates that the same observable can be used with two subscribers that listen for even and odd numbers only, respectively:


Listing 1 Interval factory

    const interval$ = Rx.Observable.interval(500);   const isEven = x => x % 2 === 0;   interval$    .filter(isEven)    .take(5)    .subscribe(x => { ❶        console.log(`Even number found: ${x}`);     });   interval$    .filter(R.compose(R.not, isEven))    .take(5)    .subscribe(x => { ❶        console.log(`Odd number found: ${x}`);     });    

❶ Two subscriptions for the same observable interval$


In the example in figure 1, the streams are created independently from one another. Each time a new subscriber subscribes, the logic in the pipeline is re-executed from scratch. These streams receive the same numbers and don’t affect each other’s progress—they’re isolated as shown in figure 3:


Figure 3 Re-subscribing to an interval Observable yields two independent sequences with the same events happening with the same frequency; hence, this is a cold observable


Definition: cold observables are ones that, when subscribed to, emit the entire sequence of events to any active subscribers.

Now, we’re ready to heat things up and look closely at the other side of the coin: the hot observables.

Hot observables

Streams don’t always start when we want them to, nor can we reasonably expect that we always want every event from every observable. It may be the case that by delaying the subscription, we’re deliberately avoiding certain events as an implicit version of skip().

Hot observables are ones which produce events regardless of the presence of subscribers—they’re active. In the real world, hot observables are used to model events like clicks, mouse movement, touch, or any other exposed via event emitters. This means that, unlike their cold counterparts, where each subscription triggers a new stream, subscribers to hot observables tend to only receive the events that’re emitted after the subscription is created, as shown in figure 4:


Figure 4 A mouse move handler generates unbounded events that can be captured as soon as the HTML document loads; these events are ignored until the stream is created and the observer subscribes


A hot observable continues to remain lazy in the sense that, without a subscriber, the events are emitted and ignored. Only when an observer subscribes to the stream does the pipeline of operators begin to do its job, sending data flows downstream.

This type of stream is often more intuitive to developers because it closely mirrors behaviors they’re already familiar with, in promises and event emitters.

Promises and HTTP calls A conventional HTTP request is cold, but not when a promise is used to resolve it. As you’ll learn, a promise of any type represents a hot observable because it’s not re-executed after it has been fulfilled.

Due to the unpredictable and unrepeatable nature of the data that hot observables emit, we can reason that they aren’t completely pure, from a theoretical perspective. After all, reacting to an external stimulus, like when a button is clicked, can be considered a form of side effect dependent on the behavior of some other resource, like the DOM, or time. Nevertheless, from the point of view of the application and your code, all observables are pure.

Unlike cold observables, which create independent copies of the data source to emit to every subscriber, hot observables share the same subscription with all observers that listen to it, as shown in figure 5. Therefore, we can conclude that a hot observable is one that, when subscribed to, emits the on-going sequence of events from the point of subscription, and not from the beginning.


Figure 5 Hot observables share the same stream of events to all subscribers. Each subscriber starts receiving events currently flowing through the stream after they subscribe


Whether an observable is hot or cold is partly related to the type of source it’s wrapping. For instance, in the case of any mouse event handler, short of creating a new mechanism for handling mouse events, an observable abstracts the existing addEventListener() call for a given emitter. The behavior of mouse event observables is contingent on the behavior of the system’s handling of mouse events. We can further categorize this source as natively hot, because the source determines the behavior. We can also make sources hot, programmatically, using operators as well.

On the other hand, observables that wrap either a static data source (array) or use generated data (via a generator function) are typically cold, which means they don’t begin producing values without a subscriber listening to them. This is intuitive because, like iteration, stepping through a data source requires a consumer or a client.

A key selling point for using RxJS is that it allows us to build logic independently of the type of data source we need to interact with—we call this a unifying computing model. A source can emit zero to thousands of events, unpredictably fired at different times. Nevertheless, the abstraction provided by the observable type means we don’t have to worry about these peculiarities when building the logic inside the stream, or within the context of the observable. This interface abstracts the underlying implementation out of sight and out of mind – for the most part.

Definition: hot observables are ones which produce events regardless of the presence of subscribers.

In general, it’s better to use cold observables wherever possible because they’re inherently stateless. This means that subscriptions are independent of each other, and there’s less shared state to worry about, from an internal RxJS perspective; a new stream is starting on every subscription.

Hopefully, you’ve gained a bit of insight into hot and cold observables from this article.


For more, check out the whole book on liveBook here.