Make Observables hot only where necessary

André Staltz
InstructorAndré Staltz
Share this video with your friends

Social Share Links

Send Tweet

Operators like publish(), refCount(), share() make it easy to convert a cold Observable to a hot one, and are often necessary to get some feature done. In this lesson we will learn when exactly do we need to convert to hot, and when can we leave the Observable cold.

[00:02] If you have used RxJS long enough, you have probably heard of the idea of cold and hot observables. It can be confusing, but we're talking about how to make a shared execution of the same observable from multiple subscribers.

[00:14] The simplest example of this is when you have a clock observable that ticks every second, and we have six of those events. Now we have here two subscribers, a and b. Once we run that, each of those are going to get a different execution. If this is familiar to you, this is the usual problem.

[00:34] We solve that by sharing that execution. Here, we can add .share() at the end, and now we're going to have a common execution of this clock observable shared to these two different subscribers. Now they're going to see the same events at the same time, like that. It's easy to stick to .share() without knowing what it does. I've seen cases where everything is .shared() where it doesn't need to be.

[01:03] In order to avoid sharing everything, it's easy to remember that subscribe means invoke the execution of this collection so we can observe it. Without .share(), we're saying here, "Please, I want the values that come from this collection and deliver them here." Of course, if we say subscribe twice, we're invoking two different executions of that.

[01:29] If you keep this in mind, it makes it much easier, because when we add .share() here, we're saying, "Instead of invoking a new execution, please share the execution of the observable if it exists."

[01:46] Let's look at a bit more difficult example where it may be tempting to .share() everything. Here we also have a clock observable, and we have another observable called random numbers. We're mapping each of those to a random number. Then we have two other observables -- one of them gets random numbers smaller than 50, and the other gets random numbers larger than 50.

[02:13] ToArray() simply collects all of them in an array so that we see all of them together at the end when it completes. Let's run this and see what happens. The clock is generating events over time. Then we're mapping each of those to a random number. When that completes, we collect all of those numbers into small and large collections.

[02:33] Notice we have some problems here. We have four numbers in total in small and large category, but we had originally six random numbers, as you can see here. Also, these numbers don't actually occur in this original collection, and neither does 57.84 happen here. This is very strange. You may find this type of behavior in an RxJS app, so it's usually the case that we're not sharing executions here.

[03:05] What's going on here? First of all, when we do subscribe, remember we're invoking, "Please, give me an execution of this collection." When we do this, we're going to look here, "Give me an execution of this collection. Give me an execution of that collection. Give me an execution of this random number," which will finally ask for an execution of the clock.

[03:29] This is going to have its own execution. Each of these maps will be totally exclusive for large number subscriber and the same thing for small number and random number. We essentially have here three executions of the clock and three executions of the random number. That's why we have all of these different random numbers.

[03:55] It's easy to instead put .share() here everywhere. Sometimes people do this like that in an attempt to ignore where the source is and try to share everything. To some extent, this may solve the problem. As we see here, all of these numbers correspond there, and they're the same, but this is an overkill, and we don't need to do this. We can just share what is necessary.

[04:29] Let's look at what is it that we need to share exactly. It all has to do with what kind of side effects happen once we subscribe or once something happens in this operator. For instance, this operator will always give out the same output for the same input. For instance, if we give x is 20 here, it's always going to say true.

[04:54] On the other hand, when we invoke this function multiple times, we get different numbers every time. This is a side effect. We probably want to share this part, for instance. Also, once we subscribe to an interval, you know that internally it will do setInterval(). That's also like a side effect because it's creating a pseudo-thread. It's creating that thing, so it's not so pure.

[05:21] We could probably already conclude that this part needs to be shared, because once we call setInterval(), we want that to be shared. Then take(6) will always do the same thing, so we don't need to share that. That's not enough, because let's see what happens.

[05:41] We get the numbers 61, 8, 3, 85, but those numbers are not corresponding here. The reason for that is that once we call a small number subscribe(), we're saying, "Please give me the execution of this." Then it's going to invoke the execution of this, and then finally invoke the execution of this, and invoke the execution of this map based on this clock.

[06:06] Even though we're going to share the same setInterval(), but we're not sharing this map, so each of these subscribers is going to get a different execution of this map operation. That's why we also need to share this part. Then, once we do that, we see 81, 30, 60, 76, and those numbers appear here.

[06:33] It's a good idea not to call share() everywhere because every time you call share() you're actually creating a new subject, and that might cause maybe problems with garbage collection. It's generally wise not to do operations that we don't need to do in the first place.

[06:51] The tip here in order to avoid cold and hot problems is to remember that subscribe means invoke an execution of this collection, and then all of these executions are changed throughout these operators. Some of those executions we want to share, such as, "This random number, I wanna share it," or, "The setInterval() that's inside this creation, I wanna share that as well."

[07:18] The other bits are going to behave exactly the same way no matter if you share them or not. That's why we don't do it. Over time, you get to develop a sense of what should be shared and what should not be shared, depending on these side effects that may occur.