What is RxJS and how crucial addition it is to reactive programming? Well lets find out in this detailed article about it. RxJS is more than 9 years old (yes its true!) and when it came most of us had questions, that had us confused about what it really is and was it even necessary to have it? So, in this article we will focus on answering these questions.
Imperative Programming Vs. Reactive Programming
Most of us are used to thinking like an imperative programmer while devising logic for our code and are unable to comprehend what an observable really is, which is fundamental for reactive programming. For instance, dependencies which are basically events occurring in return of a certain condition. So, let’s code for a drag and drop example. You will have to analyze the situation at hand, identify what event works as a consequence of the other and therefore, carefully construct your observable.
For imperative programmers, you can think of observables as such, that every variable stored has a value that changes over time and is “observable”. Therefore, through reactive programming, you can set the values to change and therefore update accordingly in all of its dependencies when the value has changed.
Use of observables, operators and much more in RxJS
Consider a code where you are calling a function with a variable c, which comes from adding a and b. So, if you need a stream of c, you will need to combine the streams of a and b and update them accordingly.
Operators can be confusing but simply if you have a look, observables without operators are the same as promises, so if you understand the latter, you would quickly catch up with observables.
Sometimes people are confused about as to which operator to use while making a reactive program. So, let us go over operator agony. You can find the guide for operators at reactivex.io/rxjs. Another thing is that you don’t have to Rx everything. Only take operators you know to point at certain things and do everything else imperatively. Some starter operators include:
Demystifying observables is something which scares a lot of people generally as to what is inside it. An observable is simply a function that takes an observer. Whereas, an operator is a function that takes an observable as its value and returns an observable as well.
Let’s chain two maps, or more to wrap. This can be done through nicer API of dot chaining by wrapping the observable function in a class. We call this subscribe. We can add operators to the class and chain them together. Observable as a class provides dot chaining, ability to safety to type, performance optimization, and all together it is a wrapper function. The operator takes an observable and returns a new observable. Functions don’t do anything until they get a value that’s why observables are called lazy. So, an operator say map(), basically creates a function which calls on a couple more observables in the consequence, which are all linked together.
So operator is basically a function that sets and connects observables.
So let’s visualize this chain. I have observable interval function, I will first filter, then map then subscribe to it. I have four observables here. One on the head for my producer, one for filter operator, one for map and one at the tail to wrap up handlers.
Observables will no longer pass a value along after either the error() function has been called, the complete() function has been called or it has been unsubscribed. Say an interval function is there that prompts an error at the value of 1. So when zero is passed the chain works smoothly but when the 1 is passed on as a value, then an error is prompted which breaks the map head which becomes unable to receive any more signals and everything before it doesn’t work anymore.
Therefore, it just passes on to the next head about the error received and therefore runs the error function.
In Angular, there is an operator called catch which does the work. Send 1 through catch and then observers from this point and up are dead and therefore, doesn’t entirely shut off like that.
So the error path in catch will map this to a new observable. To ensure your observable doesn’t die we make sure that our observable is chained through, so it doesn’t get closed by encountering an error value. If we send in one through the observable, both the AJAX and the observers would close down, creating a dead observable chain. So now nothing can be passed on through from the chain. Let’s try the catch observer. If you try creating an empty observer and mapping the value to it but that still won’t work out and the observable will still end up dead.
So, let’s try this. Put catch inside switchMap. Doing so will create two observers, one is AJAX and the other is empty so in case of error, the AJAX dies and the empty does as well but a new empty is replaced in order to fill into the behavior switchMap follows.
This kills the extra chains but the original chain is still working and keeps on doing so even if faced with an error value of the interval.
So summarizing what we have to do is:
- Created a second observer
- Punctuated the second chain with a catch
- Shielding the main observer chain
So, when handling error you would want your catch inside of a merge operator unless it is a case where it is okay for your observable to die, then you can choose whichever way to handle the error you want. But otherwise, merge operations is the key.
Now for subscription management mistakes. Failing to unsubscribe will cause memory or resource leaks because it tears down the resources you set up while subscribing. One way to do it is to manage the subscription imperatively.
Initiate a subscription then manually unsububscribe it. But when having alot of code and a lot of subscriptions to deal with, it could be that you miss out on someone and have your program can probably crash due to memory leaks.
Another way to do it is declaratively. You can choose to use takeUntil() to kill the subscription either all together or manually. Other ways include take, first, takeWhile, switch, and switchMap. DSM can be summed up as:
- Fewer subscriptions to manage
- Less likely to miss out on unsub
- Can use any event stream to trigger unsub
- Many composable ways to do it
Another way to deal with subscription management is to let your framework or libraries do it for you. For instance, Angular Pipe Async. Pipe Async gotcha has a timer fired when logged in that is because we have initialized the function subscription twice.
And since observables are simply functions, therefore, they just work that way and run all the way every time they are subscribed. To fix this we can add a share which will multicast observable to prevent more than one subscription. Though multicasting comes with a cost, which includes having to allocate an array or vector to stuff all your observables in. So it is not recommended to overuse the share() for multicasting. Now Pipe Async has:
no subscription management at all, automatic unsub or unmount
encourages overuse of multicasting, this can impact performance
Here is a subscription rule of thumb: more than two are probably too many. So, when I find myself using 3-4 observables in a module or component, then I think to myself that maybe I don’t need these or to handle all of these subscriptions. I can simply use a more declarative approach instead.
Here is an ending note, don’t Rx all the things. You can build your app as one big observable but it is discouraged to do so. Because when you do that, the code becomes really hard for anyone else to read or understand. Since Rx is domain specific and takes time to learn, it is suggested that you use it where it fits the best or is best suited. For example:
- Composing multiple events together
- Good for adding delay
- Client-side rate limiting
- Coordinating async tasks
- When cancellation is required
Now here is when NOT to use Rx:
- Simple button clicks
- Basic forms use
- Hello world apps
Let us recap all that we have so far learned in thinking reactively:
- Work backwards
- Any variable is basically an and can be an observable
- Observables are simply functions
- Observer chains do all the hard work
- Calling error() breaks the chain
- Know your subscription management
- Just use Rx where it makes sense