At my job we went from using observables for everything to literally questioning every single one that shows up in a PR. Most of the time they don't need to be there. Several spots in our app are nigh unusable/untestable messes because of the side effects that get caused from subscriptions. Eg, you make a change in one place and something else totally unrelated gets updated. Obviously the implementation was totally wrong there but I tend to believe in the idea of a "pit of success" with framework api's. With observables, it's a giant foot gun if you don't understand what you are doing and you are better off using something more stateless like a promise or just a simple javascript object reference shared via singleton service or something. If you aren't careful about managing your "source of truth" you are gonna have a bad time. Then of course theres remembering to unsubscribe from them... memory leak anyone?
While I think they are a powerful tool, I find that a lot of developers get them confused as a drop in replacement for promises and do things like nest subscriptions etc. For most standard CRUD apps, either do as the author suggests, and use an async pipe in your template, or, convert them to promises as soon as possible.
What are they good for? Streams of data. Web Sockets, iframe post messages, event handlers for instance. Anything that is actually real time and makes changes without deliberate action.
At my job, we’ve been using observables in our React front-ends for at least the last three years. These are high traffic consumer websites. We’ve had great success with them and haven’t experienced any of the issues you describe.
I guess the key is to minimise the amount of code that directly subscribes, don’t mutate data as it flows through an observable, and where possible, prefer deriving one observable from another over using imperative code to push data from one observable into another.
In React you can use higher order components and whatnot to handle subscriptions, and that capability most unfortunately is not available on all frameworks. Perhaps most egregiously, Angular doesn't really have any concept that works in place of HoCs, though at least you can sometimes fold observables using the async pipe.
Can you give some non-trivial and not excessively contrived example of when observables should be used? I'm genuinely asking because, while I find them elegant, I still can't figure out why they were put front-and-center of the current generation of web frameworks (especially Angular). They seem a good tool to have but mostly for very particular cases.
I've had a lot of success using observables for side-effects in Redux and NGRX. It's flexible, testable, and reliable.
I can map, filter, etc. my app's actions to perform any side-effect I want, possibly sending a success or failure action on completion. I can even listen for changes in the store or other streams to dispatch actions.
You can still do everything you need with Thunk, Sagas, etc., but I enjoy the ease and flexibility that RxJS gives me.
We are using reactive programming basically everywhere. Example (Angular):
The Angular Router has an Observable "paramMap". We don't subscribe to it but are creating another Observable for the data that should be loaded.
getData$ = (
paramMap$: Observable<ParamMap>
): Observable<MyData> => {
return paramMap$.pipe(
// Create a query string
map(paramMap => {
// some pure function - business logic
return getSomeQueryBasedOnTheRouteParams(
paramMap
);
}),
// get the data
// select$ is a method on the customStore that wraps the http client and does some more stuff
// The switchMap is actually one of the concepts that I found initially not the easiest to understand
switchMap(someQuery=> {
return this.customStore.select$({
query: someQuery
});
}),
map(rawData => {
// pure function with business logic
return modifyData(rawData);
}),
shareReplay()
);
};
Then it goes on with filtering, etc.
getFilteredData$ = (
// filter:$ is in our case a simple Subject we're calling next(newFilterValue)
// NGRX, NGXS or Akita are popular state management libraries to manage this kind of state
filter$: BehaviourSubject<string>,
data$: Observable<MyData>): Observable<MyData> => {
combineLatest(filter$, data$).pipe(
// Do the filtering using pure functions
map( ...
return myFilterFuntion(filter, data);
)
)
};
The filteredData$ Observable can be consumed via async pipe in a component.
The business logic is the same as in imperative-pull code. But using the abstraction of RxJS a lot of boilerplate and indirection vanishes. RxJS is not the easiest abstraction to learn (for me), but as with all, once you're comfortable a lot of the likely intimidating code above will become familiar. You just focus on writing business-logic in pure functions and combine it with RxJS.
>> because of the side effects that get caused from subscriptions.
Hmmmm, I wonder how a subscription can cause side effects? I can only think of mutating the data in the subscription. But that should not be done.
We are using RxJS with great success but you should go all the way with Observables: All computations/combination/etc. should be done in pipe()s. If you need to combine some plain data with Observables create a subject for the plain data and pipe(combineLatest(),map(), ...). Don't mix it into subscriptions. Subscribe at the very end of the chain. No modification in subscribtions.
About reactive WebSockets, I wrote a client and server library which lets you consume WebSocket data streams in a reactive way using pure async/await syntax (no callbacks, no Observables): https://asyngular.io
It encourages the use of IIFEs as a way to declaratively isolate parallel async operations from serial async operations. Also it shifts control to the data consumer instead of the producer; this should reduce the likelihood of memory leaks.
There’s one thing I find weird. There are very few, if any, resources that go beyond the simple “set up an observable, run through at most two transformations and at most two subscriptions”.
That’s where the vast majority of tutorials, blog posts and presentations end. And then you’re stuck with trying to implement a simple login flow, or reading data from a file, and... nothing really works, everything is overengineered and impossible to debug and trace.
In his book the author of RxJava openly admits it took him several months to grok reactive streams. And he was taught by the author of reactive extensions for dotnet.
Programming with reactive stuff is essentially async programming (never easy) where your data is handled, transformed in async blackboxes (hardly any implementation of Rx has a good way to observe, debug and trace data through them), and is delivered to sinks/subscribers in async manner.
And yet basically everything you find on the topic will cheerfully tell you how easy this stuff is, and present you with a toy code that fits on a screen.
I've been using Observables pretty exclusively for almost two years now. What I have now is a cookbook of use cases that I refer back to by looking at what I did in previous projects. That's not to say there's no original thought in new code that I write, just that sometimes the nontrivial cases have escaped my memory. Some examples:
* How do you end the observable's subscription when any of X, Y or Z happen (and at least one of these is a timer)?
* How do you emit N network requests in series, without nesting subscriptions? (ideally you won't need to, but not all APIs are well behaved)
* How do you transform a node-like stream to an observable?
This is just from last week, and some I'm able to do from scratch better than others.
TESTING them is challenging as well. There are non-obvious failures when you use the typical unit testing patterns (a testing framework will report that function was/wasn't called and you're not sure why, because per the test setup that shouldn't be what happened). Almost two years in and I have no idea what the marbles mean, but soon I'll start trying to grok them
But there's no way I see how I can do my job without them. The applications I work on take updates from 1) keyboard 2) mouse 3) direct websocket subscriptions 4) indirect websocket subscriptions 5) xhr calls 6) other messaging patterns that aren't as common.
So I'm at the point where I can easily tell you the differences between switchMap, concatMap, and mergeMap (of which flatMap is an alias), easily pull out filter, map, combineLatest, tap, etc. Sometimes when I'm in the weeds it does help to think that they're just functions, but at no point would I dare tell anyone that this stuff is easy. It's easily the hardest thing I've learned since I started my current job.
I was learning rx.js for new project in angular 2 and learnt two things:
1. Observables are best when used as Behaviours from classical FRP formulation by C. Elliot. It's a value that's changing over time, and you should only compose it (map, combineLatest, switchMap, merge ...) and push subscriptions to the edges - for example asyncPipe in angular. That makes writing "reactive" (as in Excel cells) code a breeze. Reacting to changes as events (like button click) should be handled with care, with first/takeWhile and so on. But simple cases are still nice looking.
2. Observables are bad as framework for coordination problems. Complex flows of events can easy get out of hand, and suddenly you are passing objects with values and some state through streams or nest switchMaps instead of piping them. Often such code could be written with async/await and would be ten times cleaner. So any code which dealt with changes as events and had branches/state because of that, we rewrote with async/await. Examples are POST/PUT/DELETE requests with logic around authentication/authorization/timeouts. You could model that as stream but when you access closure from outer switchMap it's starts being unwieldy.
It's ironic that the author's recommendation of using `shareReplay(1)` actually leaks a subscription as well. Unless the operator is a supplied with `{ refCount: true }` config parameter, it does not unsubscribe from the source observable, even when all its subscribers unsubscribe: https://github.com/ReactiveX/rxjs/blob/master/src/internal/o...
Sadly, issues like this seem to crop up all the time when using reactive programming. I'm not as anti-reactive as most other commenters here seem to be, but there's no doubt that certain areas, particularly memory safety, sharing and buffering, seem hard to understand and get right.
My bad. The recommendation at Google is to use publishReplay + refCount precisely for that reason, but surprisingly there's so little public documentation on e.g. the publishReplay rxjs operator, that I decided to go with what was ultimately more prevalent online.
Probably worth fixing that though, and creating those resources out there.
After over two years heavily using RxJS in Angular for all kids of stuff (I once mapped a 100+ step-long Excel formula with success), the only advice I can give is to make a single pipe for a single purpose, using map() and friends to run data down the pipe and performing the decisive final action in the subscribe, possibily a one-line action.
Doing this reduces risk of side effects and help reading the code/debugging.
Also you must take in account stuff like back-pressure and multicasting observables, which are as useful as hard to master and maintain correctly.
Really once you stop putting subscriptions in subscriptions by using the switchMap or switchMapTo operators, a lot of errors start to become clear and avoided.
That article seems to take a very specific stream implementation as the basis for those purported differences.
There are many others, and then the differences go away.
In fact, Rx can be traced directly to the synchronous dataflow[1] of Lustre[2] and Esterel[3], which in turn goes back to Lucid[4], "The Dataflow Programming Language"[5].
It took a bit to ferret this out of the publication record, but when I asked Erik he confirmed.
Right-- I don't disagree. But I do think 'Observable' is precise here: some streams are lazy, some streams involve pull rather than push, but if someone says "Stream" as a datatype, you don't necessarily know what they mean.
But sure enough, if all we care about is a "collection of observed values, asynchronously" 'Stream' and 'Observable' fit the bill in all(most?) implementations.
While I think they are a powerful tool, I find that a lot of developers get them confused as a drop in replacement for promises and do things like nest subscriptions etc. For most standard CRUD apps, either do as the author suggests, and use an async pipe in your template, or, convert them to promises as soon as possible.
What are they good for? Streams of data. Web Sockets, iframe post messages, event handlers for instance. Anything that is actually real time and makes changes without deliberate action.