Why reactive programming and RxJS?
If you just overheard RxJS or reactive programming and are wondering what that is or why you want to care about it, this article is for you. I would say RxJS is one of the most intimidating library I’ve seen, and the so-called “think reactively” is really not that intuitive, at least in the beginning. RxJS has a really steep learning curve, and if I need to learn all this, I need a good reason. This article is not going to teach you RxJS, but tries to motivate you before you even drill into all the details.
Key concepts of RxJS
In short, reactive programming is to treat data as stream over time, transform stream into usable format, and use stream in observer pattern.
The whole bunch of document of RxJS is about three parts.
Initialize stream from raw data. Stream is the number one important concept in RxJS. Everything is stream. Compared to “normal” data, a stream a series of data over time. In other words, a stream is data set with time dimension. We will see why this matters later.
Operations are to transform stream into another stream. Often, the stream initialized from raw data does not have the data structure you need, and you must turn into another stream you can utilize directly. RxJS exposes an
Observable object that includes a lot of operation utilities (which makes the document look so overwhelming…)
To use the stream, you need to subscribe to it. This is known as “observer pattern”. You must subscribe to an observable, and do something accordingly, or “reactively”. That’s why it’s called “reactive programming”.
Also note the observable is “lazy”, which means that if you do not subscribe to a stream, nothing would happen to the stream. We will see why it matters later.
I have purposely not included any code yet because I don’t want to overwhelm you with details. Now that we understand the basic concepts in RxJS, let’s see what it can do.
Out of order asynchronous request
Think that you have two asynchronous request that you want to keep in order. Since the time of response depends on bandwidth and server processing, you can’t really control which returns first. Worse, it is likely that one request would make the other request invalid. And when your app innocently handles the invalid request, things would go wrong.
An example is autocomplete. Below is a simple example for demo purpose. As user types, we mimic sending a request. The result (which is simply an echo in the demo) shows below the input. We manipulate the response to delay 5s for the first type, and 2.5s for the second type. Therefore when we type “1” and then “2”, the server would return in reverse order, and the later response “1” would replace the earlier response “2”, resulting in displaying “1” at the end. This is definitely not what we want.
How should we solve this issue? We probably need to listen to the “keyup” event in input, and whenever a new “keyup” event occurs, we need to cancel the service request first. Well, I mean, you can never really “cancel” a request that has been sent out. The server will still receive, process, and return the request, but the client will not do anything with the response. Thus you are actually canceling the handling of the request response.
But it’s not straightforward.
Promise does not support cancellation out of box, although we have a workaround.
XMLHttpRequest has an
abort method, which can cancel the request.
With RxJS it is way simpler. See code below. Note that the server still returns two responses, but the client only takes the response from the latest request, and the invalid request is ignored. This is exactly what we want!
This is how the magic happened. You create a stream from “keyup” event. Then you “transform” this stream to a response from service. By using
switchMap, you drop the event stream and only care about the service stream. With every “keyup” event, you get a new element in service stream. And remember subscription is lazy? RxJS would only do things to the latest request. This is pretty neat!
With RxJS you can also easily do debounce/throttle. This is really straightforward since RxJS is all about time!
Now think about animation. What really is animation? Essentially, animation is about position over time! So animation can be treated as a stream! Rx provides
Rx.Scheduler.animationFrame to emit data every animation frame. If we know the start position and end position, we can do interpolation based on the time interval, and render the positions in subscription. Pretty neat! Check Ben Lesh’s video for more details.
Use with React
I see a really potential use case when combining React with RxJS. React renders a deterministic UI given a state, and renders another UI given another state. However, it does not have direct support for how UI changes when one state changes to another state. This is sometimes important when you want to give user an animated transition between two UI rather than an abrupt change.
In the RxJS world, state changes can be seen as a series of states over time; In that sense, state is a stream! To use with React, we need to hook up stream with setState, so that every stream value can trigger a re-render. See this and this for some experiments. However, in my opinion, these approaches are still too complex.
In the end
So hopefully you see the power of RxJS. Despite that, the disadvantage of RxJS is also obvious: the pattern is not straightforward. For stream transformation, there is usually more than one way to accomplish; you need some level of expertise to tell if one is a good “reactive” way. It is difficult for beginners or junior engineers to adopt, thus challenging for team collaboration. Many times you feel it not worth the effort to get all in RxJS because most situations are not that complex (e.g. having racing async call issues). Maybe it’ll be better to use RxJS underlying for some complex issues than to expose it to developers. Still I think we can learn a lot from the reactive pattern, should try to integrate it, and perhaps develop some higher level API to make it more usable.