Rxjs — be careful with that axe, Eugene
Since angular 2 brought Rxjs as embedded library, it automatically moved Rxjs to mainstream. However Rxjs is fantastic and very powerful library, there is a trade-of related to that. Rxjs is not super new concept. I have seen it couple of years ago in C# community. It raised up, as some hyped library for data flows and then moved into shadow. I was reading about that and listen a lot conferences, because a lot of people were talking about that, at these times. Eventually I never really used it in real scenario. It was one of these libraries to show on presentation, to make wow effect. It shines when you are developing some ETL or building up “typeahead” like control. However it’s really hard to find some reasonable usage of that library. You need to be familiar with some functional language, unidirectional data-flow and reactive programming concepts to use it properly for your architecture.
These days functional programming is gaining more and more popularity. Angular team embedded Rxjs library inside their framework so it’s why I expect Rxjs to be even more popular. Because of that i want to share with you my painful way of blood and tears to make friendship with that library eventually.
When i started working with Angular i have seen that they wrapped Http service in Observable pattern. I started thinking to myself why the hell they did it to me. Why i need to call toPromise() or why i need to remember subscription somewhere to dispose it later. Why this bloody angular team did my work harder. It was not event natural to me and seemed like trying to put rectangular blocks into triangle slots. Http request is just one time async shot. It’s not behaving like a stream. Creating stream just to put one item on it, looked odd to me. Then i discovered unidirectional data flow concept and i was trying to make my application swim by. I was overusing Rxjs for everything. It’s the same funny level like when somebody you just discovered factory method pattern and tries to put it everywhere. Pretty quickly my code started to swim all the way around. It was so fun, that when i drop one item on Rxjs pipe, it was always giving me some unexpected surprising behavior. One object thrown in one place of code resulted in two other unrelated popping up in other parts of application. I was throwing multiple times item to Rjxs pipe looking from other side of pipe to object finally go out, but it never happened. I was sad sitting in a corner and crying. My pipes were dying after 500 errors from server, even though I told Rxjs to be polite and catch my exceptions. It wasn’t listen to me at all.
Lesson learned from that experience:
- exception thrown from Rxjs is killing whole pipe. It doesn't matter if you catched exception at all. One thing you can do with catch operator is to create one observable and replace broken one with newly created one. To prevent whole stream dying, you should use ‘potentially exception throwing stream’ within higher order operator like switchMap, mergeMap.
Please notice in above example, that catch statement is defined on inside stream. Inside stream can be killed by exception, with no regret, because it will be recreated by switchMap, when new object appears on parent stream. If I would write same thing with catch statement defined after switchMap operator, my stream would die after first 500 coming from server. Catch in Rxjs is only for replacing corps of dead stream with new stream, not for rescue it.
2. When you use swtichMap or flatMap or other higher order operator with cold observable as argument, it will change nature of whole pipe to cold. On above example fetchDataClick$ is hot observable. If I will start throwing items on fetchDataClick$, no request to server will be triggered. It hapens because nobody is listening from other side of pipe and Http is cold observable. It will start work only when i will subscribe to whole pipe. Normally I wouldn’t expect that, because fetchDataClick$ was hot. My natural way of thinking was that if i am building pipe based on hot observable then whole pipe will be hot as well.
3. Next i wanna expose that stream from service to all interested subscribers. Obviously I need to terminate this stream definition with
share() or with
publishReplay(1).refCount() It seams easy-peasy. It will prevent us from triggering request to server for each and every subscriber. This will also return latest cached result to every component, who subscribe. But there is a trap. refCount operators is automatically executing connect(), when first subscriber will appear and will disconnect() when last will unsubscribe. Let’s imagine some usage scenario. First component is subscribing to stream. Then you navigate to other component. First component is unsubscribing. Next component will subscribe again. In this short moment of transition between component there is no single subscriber. During that moment refCount will call disconnect(). It has some implications, like its gonna clear distinctUnitlChanged() cache. To prevent that I am always exposing streams from service as BehaviorSubject(). I keep my big pipe definitions as private and i just throw items from internal pipe to public BehaviourSubject like below:
Other option to omit that problem is to not use refCount(), but connect manually published stream like below.
These are patterns I was using and it saves a lot of frustration when dealing with Rxjs.
4. I have found somewhere in the internet these super useful snippet to debug Rxjs. Instead of writing everywhere do(console.log.bind(console)) I use .debug(“i am here”).
I relay like rxjs. It’s very powerful tools, but at the same time it’s super dangerous. I know that creator of framework Cycle.js — André Staltz — resigned from using Rxjs and switched to xstreams. Main problem which forced him to switch to xstream was confusion around hot and cold observables. I think the same. Rxjs would be much easier without cold observable, if everything would be hot like with xstreams. Cold streams are handy but are super dangerous. Similar like with two way data binding. It’s cool feature, but super dangerous.
Do you have some more pro tips how to deal with Rxjs and not hurt yourself?