- 01:25 - RxJS
- 10:09 - Observers
- 17:49 - Back Pressure
- 22:11 - Async Iterators and Generators
- 31:30 - Mapping
CHARLES: Hello everybody and welcome to The Frontside Podcast, Episode #84. My name is Charles Lowell, a developer here at the Frontside and your podcast host-in-training. With me today is Elrick Ryan. Hello, Elrick.
CHARLES: You and I have actually been on a roll lately, podcasting the hell out of these podcast.
ELRICK: Yeah, I know. That is very true.
CHARLES: It's been you and me but it's feeling great. It's good to have you on the show again.
ELRICK: Yes, wonderful, man. Let's keep it rolling.
CHARLES: All right. We will keep it rolling. Today, we are going to be talking about redux-observable and to help us understand and plumb this topic, we have someone who's very qualified to talk about it. Mr Jay Phelps, who in addition to having been the co-creator of redux-observable, also is on the core team of RxJS, which is a fascinating library on which it's based for many years and is currently a senior software engineer at Netflix. Welcome to the podcast, Jay.
JAY: Hey! Good morning, everyone. I guess it's not probably morning to the people listening but good morning to you all. Thanks for having me.
CHARLES: I'm excited about it. I know that kind of starting with the fundamentals, RxJS is something that was on my radar for a few years and it definitely [inaudible] once we started using redux-observable but the whole concept, I often feel like the world kind of is turned upside down when I'm working with observables, when I'm working with RxJS and I'm curious, how did you come to be a part of that project and what are the things that you use it to solve? Why did the solutions that you generated shake out that way?
CHARLES: For some reason, I had this impression and maybe I'm mistaken that Rx originally came out of Microsoft.
JAY: Let me continue with the story. It's confusing and I can actually take a step back and clarify that point in particular. Rx itself was originally came from Rx.net, which was indeed created by Microsoft many, many, many, many years ago. I don't know the exact date. I think it was at least 10 years ago. They, at the same time created or about the same time, Matt Podwysocki who was working at Microsoft and still is working at Microsoft, created Rx.net and RxJS. Then many years had passed and originally, it wasn't as popular as it got in the coming years. After several years, some employees from Microsoft ended up coming to Netflix. Jafar Husain is one of those employees. He came from Microsoft to Netflix and he brought that Rx knowledge and that advocacy.
Rx is very ingrained in the Netflix culture and is used a lot by various teams for various purposes. Then when I joined Netflix and I got really exposed to it. One of my coworkers at the time, Ben Lesh was asked by several people at Netflix to consider and look into rewriting RxJS. At the time, the version was RxJS 2.0 and while it was great, we had some specific requirements for our website and some of our other applications that we were hoping for a better performance, smaller bundle size and better debugability and --
JAY: Yes, somewhat. Definitely, there were more considerations this time around when it was rewritten and originally, it was going to be Version 3 but the rewriting process took quite a while as these things usually do. By the time we got a version out, it was Version 5. We started when RxJS was at Version 2 but it already released Version 3 and Version 4 by the time it released for the new version like a rewrite had been able to get out. When I say a rewrite, I mean like from scratch rewrite.
Matt Podwysocki who was the maintainer, almost the sole maintainer of the previous version, also is now on the core team of the new version of RxJS and has been instrumental in pushing back forward as well, he has far more experience with this than either Ben are I so he's been invaluable. Sometimes, we'll think to make those decisions. We'll be like, "Why was this decision made? Was it made because of .net?" and we'll just assume that and we'll want to change it but Matt has the history involved in that. He knows why things were changed the way they were.
For example, we changed one of the operators, flatMap to mergeMap. We know somewhat we go at least, I don't want to speak for the entire team but I regret that decision. Depending on the day that I've been talking to Ben, I could convince him to regret that decision as well. But we thought that mergeMap would make more sense and that very few people in practice would have heard of the word flatMap before and had experience with that so --
CHARLES: I have to say both of the terms coming at it were pretty opaque. I think there was a bout of equivalence in opacity.
JAY: Yeah, good to know. That's just an example. I don't want to stick too much on that topic. Maybe someday we'll go back to flatMap and the flatMap still exists. If you're a purist, you can use it. Ben was the primary person who was working on this. He wasn't working on it full time but pretty close to full time to get that initial version out. Even though I used it, my involvement with it was fairly low at that point and then my involvement after it was released got increased. I found more time and started to get more involved, particularly there wasn't a lot of code to write. I have some PRs and stuff like that but particularly on the planning and the issue triaging and PRs and stuff like that, which is a pain in the butt. It's just massive.
Particularly, around the same time that the rewrite was getting finished that Angular decided, "You know what? We’re going to bet on Rx. We're going to depend heavily on it," so you really can't write Angular without writing some Rx these days. You can get away with not knowing Rx very well. You could just call subscribe and then just do a bunch of imperative stuff but for the most part, the paved path is observables in so many fashions. Now, there's this ngrx. I don't know if you have any exposure to the Angular community. I have quite a bit.
CHARLES: I haven't recently. Certainly, since that kind of heavy investment in Rx, I haven't been exposed to it.
JAY: I think that was your question, right?
JAY: The idea was realized very early on --
CHARLES: Yeah, both 10 years ago, right.
JAY: Yeah, exactly. They dubbed it their reactive extensions, which is what Rx stands for. Pretty much, name a language and it's been ported to that. There's RxSwift, which is super popular. There's even things like RxCpp and stuff which if you look at it, it's awkward. It seems like we got less language in the world for doing this sort of thing. I actually like C++ in a lot of ways but it was awkward stuffing that stuff in there. It's a really popular pattern and the idea is just basically going all in on the observer pattern, saying that like, "Most people are building things in which you want to be pushed information." You want to be pushed events and the data should stream to you.
Modeling most problems in the world as a stream, once you get over the initial barrier of getting away from your normal historical way of looking at things and you look at everything as a stream, it comes very natural because you can actually model literally anything as a stream because it could be a stream of one, it could be a stream of nothing, it could be a stream of infinite number of events or it could be a stream of 10. You can model anything as a stream.
Once you start thinking about that, it just becomes very natural and particularly on the UI side of things, I think there's been a lot of success in RxJava stuff at Netflix but RxJava is also used in Rx.net for client-side stuff as well, for mobile development.
CHARLES: I remember when I first was introduced to it, I think there was a lot of confusion for me around an observer in the context of Rx and an observer in the context of classical MVC. One particular manifestation of the MVC architecture where you have these kind of mutable objects and you're observing their properties. Like key value observation, which factors heavily into certain UI frameworks. Backbone is one that comes to mind or if you're familiar with Java, basically the JavaBeans, like the property model listeners. I kind of had that conception of what an observer was versus Rx has a very, very different take on observable things. Do you think you could maybe show where they're different?
JAY: You can get the normal classic observer pattern using Rx, using a subject basically. But there's a subject class, which you're not going to use super often but there are certain cases where it makes sense. Also, it depends on what libraries. It is used more often in the Angular world because you want to get a stream of clicks or something like that but --
CHARLES: So what would be the subject in that case?
JAY: The subject in that case would be you're going to pipe, you're going to emit. Every time they click on something, you're going to 'next' something into that subject, like 'next the event' into the subject. Basically, a subject is a really great way to go from some imperative world to the observable world. Without having to write all sorts of custom glue, you can just basically say, "I've got subject. Any time I 'next' into it, just notify anyone who's listening to this." A subject is hot observable and that's the closest to the typical observer pattern because Rx, it's usually like observables and are usually lazy or cold. That's also what people call it. In the normal observer pattern, there is not necessarily any concept of laziness like you listen to something and that producer is already producing usually.
CHARLES: Right. You hit on that and I think that was something that was surprising and kind of delightful when I first started using observables is to realize that they were lazy. Let me make sure I understand it. What was cool is like I was going through some of the demos and I had this observable and is part of, forgive my terminology but when you create a new observer, you pass the function that will get called every time something subscribes to it, right?
JAY: When you create a new observer it passes a function --
CHARLES: When you create a new observable, you pass a function that gets called when an observer subscribes and the thing that you can pass is the thing that you can call next on, right?
JAY: That's exactly right. When it's a lazy observable, that only get called... Actually, you know, continue. You had a point.
CHARLES: I was going to say what was a surprising and cool for me was that every single thing that subscribe to that observable got its own history of that observable from the very beginning. It got its own function invocation so the first example I did, I wanted to iterate over an array and just send 10 items to the observer. Then when you subscribe, you're starting from one every single time: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10. It's not like I subscribe and one gets five of the elements, then I subscribe to another one and that one gets the next five or those two get the next five together. It's like they each get their own version of that observable.
JAY: That's exactly right and then that's the main difference between --
CHARLES: -- The whole thing, yeah.
JAY: Yeah. This is still observer pattern because the observer pattern, at least to my knowledge, I'm not an expert on this or anything but my understanding is that the observer pattern does not necessarily dictate hot versus cold, per se but traditionally I would say, people interpret it as typically the hot type of thing or also the multicast, if you want to be like these cool buzzwords.
CHARLES: Right. What you're thinking of it is when you've got this mutable object, you just dive in wherever it is at that moment and you're now observing events from it.
JAY: And that's traditionally because the observer pattern was mostly created for that model view controller type of pattern as well. You can think of like a stream of clicks: the users clicking on things where a stream of keyboard events. That is inherently a hot or multicast stream. You can't tell the user, "Start clicking," or, "Start typing. Don't type." You can tell them that but it's futile. The point being is that you can't control the stream. It pushes data to you and it's already pushing, whether you're subscribing or not, the data is flowing so the traditional observer pattern works really well for that. Then the observables, it's usually lazy,
Now again, with subjects and with multicasting, you can get the same behavior. You can get that observable that is hot and shares its subscriptions so that when multiple people subscribe to it, they all get the exact same underlying subscription that is already producing data.
CHARLES: I see. That makes sense with to call of multicast because fundamentally, there's really only one observer. There's only one stream and you just happened to be entering in at a certain point.
JAY: Right. Then the other one is called unicast but not everyone calls it that but that's usually what it's called.
CHARLES: There's a lot of terminology. With subjects, is that just a fancy way of saying a hot observable?
JAY: Not necessarily but almost always. If you include them together, you're not wrong 99% of the time. But as a quick drive by definition, it's totally fine. If you're teaching someone really all the things and then they want to fundamentally understand it, it's good to have some distinctions. A subject is hot and multicast but just because something is hot and multicast does not mean that it necessarily has a subject backing it. Maybe from the pattern perspective, you could call it 'the subject' or 'a subject' but in the Rx world, it's not necessarily a subject.
CHARLES: Then for cold observables, what are good things that they model, like the execution of an algorithm or something? there's an instance of that algorithm, I'm going to add two numbers or I'm going to reduce this tree of numbers into a product or a sum or something like that, where you start from the beginning, there's a clear beginning, there's a clear end --?
JAY: Right. The observables are really good at modeling side effects, for example. Things you want to do like make an Ajax call or read a file or something like that. In fact, reading from a file is actually not a great use case for observables just because you want to be able to control the back pressure. We can talk about that next if you want but --
CHARLES: Yeah. Back pressure is a concept. It's crazy to me and when people use it, I'm like, "How do you even do that?" You can say it and sounds good in practice but it sounds really, really complex.
JAY: I think I can summarize it for you. The back pressure stuff is basically you have a data producer and you want to be able to control the rate in which it is producing so that you do not overwhelm the consumer. You can consider two servers. You've got Server A and it's sending events to Server B. If Server B can only process 10 events per second but Server A sends 100 events per second, what's going to happen? There’s a deficit. There's a huge 90 events per second deficit and that means that the Server B is going to get more and more behind. Eventually, it will just fall over and run out of memory, CPU or whatever happens.
Back pressure is basically just being able to control that somehow. There's lots of ways, there's several policies of back pressure. There's buffering, there's dropping and then there's the pull model where it's like, "I'm going to tell you when to give me the next event," or, "I'm going to tell you to give me six more. I can handle six more, give me six more," or, "Here's the next one." Those were --
CHARLES: Then polling would have to be combined with buffering or dropping still, right?
JAY: Possibly but it becomes one of those like you use it just as a rarity. Because the problem with buffering is that it becomes usually unbounded and that means you can --
CHARLES: It's blowing up the producer, right?
JAY: Well, you risk money out of memory, mostly. It just becomes completely unbounded. You can bound that buffer and then it becomes dropping. Then if it's dropping, that means it's lossy. In a lot of scenarios, it's important not to drop information.
CHARLES: They're useful stuff.
JAY: Right. At the same time, there are a lot where you can drop it. Like at Netflix, we have this data pipeline thing for these logs and I got a good talk on this actually. This data pipeline is called Mantis job platform where all this data flows through. It uses RxJava almost exclusively and we have a lot of different back pressure mechanisms but the one of the most popular is dropping data. If the consumer is being overwhelmed with the number of logs and events that are matching to it, we just start dropping them because they're logs. We want to keep them but we make a best effort and it's perfectly fine to drop them.
In the observable world, it's usually going to be either buffer or drop. You're not going to usually be able to control the producer’s rate. In RxJava, there is something called the flowable, which lets you control the rate by basically saying, "Give me N number. I want N number more." It's become more important on the server-side end of things but a normal observables --
CHARLES: That makes sense.
JAY: Yeah and there's even a library that's brand new that Matt Podwysocki came out with called IxJS. Instead of RxJS, it's IxJS and it's iterator. It's for async iterator and regular iterators, which is good for that other end of the spectrum for back pressure. If you want to be able to control the producer, you're saying, "Give me the next five." That's what an async iterator is incredibly perfectly suited for.
CHARLES: The async iterators are kind of like the logical inverse of the observables?
JAY: That's exactly right. It's the dual, if you want to use the computer science term, I assume you knew that already and it was that was just a plant.
CHARLES: Actually, I didn't terminology, the dual but it's like through the looking glass world, right?
JAY: Yeah, that's correct.
CHARLES: One hand you got Alice in our world and she's looking in the looking glass world. I'm not saying which one is which but one is observables and one is the async generators. When you would want to use async generators is when you want to have the consumer driving the entire stream.
JAY: That's right. A lot of people, when they discover async iterators, they'll ask me, "Jay, why wouldn't I just always use that? It seems like they're more flexible of the two." The reason why is because you can't always control the producer. The fundamental example that I was giving earlier like mouse events or keyboard events, you cannot control what the user does so an async iterator would be a very poor primitive to model that because what happens if you decide to give me one keyboard event at a time and one per second. You're like, "Give me the next keyboard event." If you did that one per second and someone is typing on their keyboard, what do you do with all the events while you're waiting? You could buffer them but that's a back pressure problem. You have to choose and it's a very poor primitive model to that.
CHARLES: Yeah, that would be a terrible user experience. The users want the consequences of their actions to be realized at the soonest possible point.
JAY: That's exactly right. The other benefit of observables is a slight increase in performance because when you subscribe to an observable, it sets up the chain and then now only data needs to flow through that chain. Whereas an async iterator, every time you call next, you're going to get a brand new promise. If you used it for something very high volume, you might be able to see how now you've got a lot of excess allocations: CPU cycles being used, garbage collecting for each one of those promises. In Netflix, we got a lot of streams, which are hundreds of thousands of events per second. If you do that, those allocations of those promises can certainly add up. It's just not the most efficient primitives for that as well.
CHARLES: I thought that async iterators used essentially continuations under the covers and not promises.
JAY: Under the covers, I'm not sure how you would describe what --
CHARLES: I'm not super familiar --
JAY: It's basically an --
CHARLES: You're talking about like yield, right?
JAY: You're talking of generators maybe?
JAY: Iterators and generators are although related because a generator is basically a factory for creating iterators. Does that make sense?
JAY: Every time you call that factory, you get a brand new iterator out of it. But then when you have an async generator, it's basically the exact same thing except for the iterator returns a promise. You're basically saying, "Give me the next thing." You call next on the iterator and the next will return a promise and that promise will resolve with a value. Why is that useful to standardize? Because you can have syntactic sugar like the [inaudible]. I don't know if you've seen that yet but it's like a loop primitive, where you can basically loop over an async iterator without needing to deal with the whole thing and all that stuff. The same way normal async/await is helpful. This await is helpful at the same time.
CHARLES: It's way above my pay grade but maybe you're using both. I'm trying to think with async the way that works with promises, right?
JAY: That's correct.
CHARLES: So you got both the continuation and a promise. That's even more overhead than the promise because you've got to stuff away that whole call stack and the promise and yield to it. But anyhow...
ELRICK: This is very interesting. RxJS is a very deep subject. What I'm interested to know is you have RxJS, you have observables and you have redux. Where's the union? How did that come to play that you guys came up and said, "You know what? We need to create redux-observable library?"
JAY: Yeah, great question. It came about fairly organically and over many month period of time, actually. We bring Rx people as I mentioned and we were using redux at the time and we're still using redux but my team that I was on was using redux and we were using redux thunk and the thunks were getting incredibly out of hand. It was very hard for us to do things that we were used to doing that were very simple like debouncing. We were like, "This is really hard. It's just to do something so simple that in Rx, it's so simple." We try to stuff Rx into redux thunk and try to make conventions around that. It just didn't work out well and then we initially made something we called thunk-observables --
CHARLES: We really wish you would have stuck with the name even you didn't --
JAY: Yeah, so thunk-observable is probably what you can expect. You dispatch a thunk which returns an observable and that observable will be subscribed to and that was it. Basically, that was the primitive so that let us have our debouncing but in practice, we found that the thunk stuff cause way more confusion and then also did not let us do composition as much as we wanted to. We then extended thunk-observable thing to get receive a stream of actions so that you could compose the different thunk-observable together and then receive new actions to cancel things and stuff like that.
We eventually just figured out that having basically the idea of thunk-observables but having of them be like almost static factories that are like process managers, that's the pattern that we ended up on today. That we coined calling them epics. Basically, an epic is a function, which receives as an argument, a stream of all of the actions. It gets an observable of every action that's going to be dispatched and it was a hot observable so that means that if you happen to subscribe to it later, you're not going to get all the actions from before. It doesn't replay them. It's just all actions from the time you subscribe and continuing.
Then what that function is expected to return is a new stream of actions and that stream that it returns of actions gets subscribed to by the middleware under the hood and basically, it just calls stored dispatch on anything you emit. You can imagine the simplest epic would be a function that gets streamed of all the actions, it applies a filter operation on all of those and looks for a particular action and then based on that action, it performs some side effect and then when that side effect comes back, it emits a different action. Basically, to notify redux like, "Here's the results. Here’s the change. I'm done," or what have you. let you just make arbitrary side effects but isolate them in a way that fits very naturally with your UI layer that you're using and with redux itself with the purity of the reducers.
CHARLES: Right. For example doing an Ajax request, I think that's a good example. You get some action in, you're getting every single action that's dispatched to the store, the first thing you want to do is you want to filter that stream to only the actions that are user click the save button so I want to save off this form. To some action, that's my save action so then my job is to now map that eventually. Out the end of that stream, I want to have like the save either succeeded or the save failed. There's two forks on an Ajax request.
The fundamental mapping that's happening is either from the user click save, whatever you want to call that action, I want to map that to the user if the save was successful or I want to map that to the save failed or rejected. The hard part then is executing those side effects and then putting them back into the stream. How would you do that then with redux-observable?
JAY: If I'm following correctly, the typical almost all epics, not all of them but almost all, they're going to have a filter at the top of the epic, they're going to filter out looking for the action they want and then they'll have some sort of merging strategy operator like mergeMap, aka flatMap or they have something like switchMap.
CHARLES: I feel like that's one of the harder concepts for me and I want to actually going to be a little selfish here and try and bounce my understanding off of a bit so you can be like, "No, your mental model is incorrect," but we'll find out how it's incorrect. With the mergeMap, is that a way of saying, "I can take other observable streams and inline them inside this one stream." That's the way I've been thinking about it. I can go out to the rest of the world, I can have some other stream and I can inserted into the processing chain of this other stream.
In this case, I do a mergeMap of the actual fetch or the actual post but it's not a straight chain of implication like do a filter then I do a map then I do this. I've got to take this other observable, this thing, this other asynchronous process, which really represents another stream and I want to merge it into this one stream. Then once that's done, I get the result. now, once that happens outside the mergeMap, now the Ajax results whether it's a success or failure, the result of the request, now it becomes the next item in the stream, which then I have to map down to another redux action.
JAY: That's right. The mergeMap operator has an alias and it also used to just be called flatMap so you can envision that you are creating an observable of observables, a higher order of observable. With mergeMap, you want to flatten that. You're saying that every time I get an event, I'm going to call this projection function and it's going to return a new observable, an inner observable and I want to merge each one of those into each other so that it becomes one stream. That means you can have concurrency. That means you can have multiple and simultaneous concurrent Ajax calls that can finish it arbitrary times.
I think it's important then to contrast that with some of the other merging strategies like switchMaps or concatMap. With switchMap, as the name implies, you switch between observables so at any point in time, you can only have one observable subscribes to at a time. If a new event comes along and you call the projection function, it returns a new observable. If the previous observable has not yet completed, it will get unsubscribed to and anything it was doing gets cancelled.
In the example you're talking about, if you've got an epic that goes and fetches your user model every time they click this button, let's say that they can click that button multiple times and you don't want to make 50,000 requests. The quintessential example is actually the auto suggest stuff. As you're typing key strokes, if the request is in processing and they type another keystroke, you don't want to wait for them the previous request to come back and process it. It's not only wasteful because you'll process the JSON and all that stuff. It can introduce bugs because it may come back out of order. It may come back actually after your new one comes back and that can cause all sorts of crazy weird bugs. That's what switchMap is really great for. I call it implicit cancellation.
Ben doesn't like that because he's saying that in a switchMap, you are being explicit about it but you're not calling unsubscribe on it yourself, which is why I call it implicit. It happens automatically. There will never a new event pipes through there. Then there's the third one which I don't --
CHARLES: So this switchMap ever make sense outside the context of hot observables?
JAY: I would say that it makes more sense usually on hot observables but there are certainly cases like let's say that you've got a web socket observable and every time you get an event, based on that event, it make some other request and that other request may or may not take a really long time. But if you get another thing back from the web socket, you want to cancel the previous one. That's somewhat not a great example as well because sometimes people use multiplexing for the web socket so that it becomes multicast.
But the point being is that there are definitely times where you will use merge, switch or concat. That concatMap being the third one where as you might imagine, you are concating the observables, you line them up. If I get a new event, I'm going to call my projection function, create that new observable but I'm not going to subscribe to it. I'm going to keep it but I'm not going to subscribe to it and tell the previous observable I was subscribing to has completed.
In a way, you're buffering the observables and because you're offering them, you can get in trouble where you end up buffering infinite observables. Let’s say the first one never completes for some reason because of a bug, you may infinitely buffer them. ConcatMap is not used that often. I'd say it's very rarely used. Its use mainly when you don't want lossy behavior. You want, at most wants semantics like you're only doing one per time.
CHARLES: The difference between mergeMap and concatMap is what?
JAY: It's you're not going to do concurrent. That's probably the best way to explain it. You're going to do that in sequence instead of concurrently.
CHARLES: I see. Maybe an example would be like file uploads or something. You probably want to do your file uploads in parallel but let's say, you want to conserve bandwidth or you're working where you've got a browser that only supports 10 connections or five upload connections but when someone selects 30 files, you don't want to just drop those files. You want to be uploading 10 concurrently but as soon as one finishes off of that 10, you want to start up another one.
JAY: That's absolutely right. Another example would be like you are going to hit some API and it could be used as one mechanism to kind of throttle yourself. If you want to guarantee that you're only connecting at most once to this particular end point at a time, no matter what the upstream tells you to do.
CHARLES: Right. I could see that. That makes sense.
JAY: I can tell that you're a little bit struggling to see the use cases, which is totally normal. ConcatMap is not used very often. It's for of those fundamental operations, which primarily why it's included is it's non-trivial to implement yourself.
CHARLES: Are there any other mapping operators that we should know about?
JAY: Those are the three primary ones. There's a couple of other like boutique variations but they're all variations on the exact same thing.
ELRICK: Do we have like mergeMap, switchMap and concatMap that are specific to management of observable and that's specific to redux-observable. There's people out there using something like redux-saga or they're trying to compare and contrast these two libraries. How do these things contrasts? Because I don't think you would use either of these mapping operations inside of a saga. Is that correct?
JAY: I just want to make a minor clarification. The Rx stuff and these operators that we've been talking about: mergeMaps, switchMap and concatMap, they are not actually related to redux-observable really in any way. The only thing they're related is that you just might happen to use them. Redux-observable is actually a very tiny library and it defers pretty much everything to just normal idiomatic RxJS code and that's really the biggest pro in comparing it to say redux-saga. Redux-saga came significantly over a year before redux-observable and without a doubt, were influenced. The pattern is very, very, very similar but there are some just fundamental differences and one of those is that difference.
The most obvious thing is if you already know RxJS very well, without a doubt you're going to want to use redux-observable. I could tell you that, it's not possible but it's very unlikely you'd choose redux-saga over redux-observable if you're already a really big Rx fan because you basically know how to do it. You just have to think that the items you're modeling, the events you're modeling are actions, which just happens to be a convention really.
There are some other differences though between redux-observable and redux-saga and the biggest difference being that redux-saga takes this effects as data approach, which is like Haskell if you're familiar with that. In a lot of ways, it's identical with the IO monad but it basically just means that you're not actually performing the side effects yourself right then and there when you call their operators. There are special utility functions, instead there's like an engine or a middleware underneath that performs the side effects on your behalf.
You create a generator and that generator is the pattern that is called a saga is what they call it. That generator yields these objects, these effects objects. One of the effects objects might be to call some particular API to make an Ajax request so you're going to yield that object that basically describes the side effect you want to perform but doesn't actually do it itself. It's just an object. In the middleware will form it for you so why would you put that indirection.
The indirection helps when you want to do things like testing. If you want to test it, you don't actually need to perform any of the side effects. You can just call next on the iterator that you get back from the generator, you call next on it, you will get the side effect object, the effect as data that you want to perform and you can just assert based on that. You don't need to do mocking in all of that stuff. You just have to assert that the effects that were yielded were the ones that you expected.
Now, I don't like that approach personally because I actually use redux-saga quite a bit, not lately. It was like a year ago. You end up implementing almost all, not all but a lot of your business logic in your actual tests themselves. Your test become less about testing the behavior or the outcome of a particular thing and more about testing how it gets to that outcome and what steps it takes to get there. Some people think that's fine. For me personally, it felt like any time I made a change in my saga, I had to make the exact same change in my test even though the behavior of the saga did not change. The actual observable outside side effects did not change in any way but I may be refactored or renamed something. It felt very redundant and to me, felt brittle because I started to wonder who tests the test then.
If the tests are in a lot of ways are reimplementation of the saga, how did I really test that the behavior of what I was trying to accomplish really was accomplished. I'm not an expert on it so certainly, I'm sure there are people who have patterns around this that can mitigate some of my concerns. But for me, I'm used to testing Rx and I'm used to Rx in particular so the pattern for me of using observables just made a lot more sense to me. Also, with either of these libraries, the learning curve is really steep. If you don't know redux-observable, let's say you don't know Rx, learning Rx just to use redux-observable is a pretty huge undertaking. I would usually recommend people to not do that.
A lot of times these days is spent me helping people who are frustrated because they dug themselves in a hole by using all these new technologies. They'll pull in TypeScript and [inaudible] with every all of the cool new hotness and I don't blame them because they've been told that this is all the cool stuff.
CHARLES: We got some great advice on that that you can have one vanity technology on every project and live it yourself. Indulge in that coolness, in that hotness and do something exciting but make sure you have one vanity technology and one only. Everything else, be very comfortable with and one is just crazy experimental like this is the coolest new thing and we're going to just drop it in. But if the rest of your chassis is solid, then you can support that crazy experimental engine that requires you to stand up on the front of it and feed gas in with your mouth like they did in Mad Max.
JAY: And there are exceptions obviously but for the most part, I subscribe to that. I answer a lot of questions on Stack Overflow and redux-observable. I actually get sad and frustrated when I see people that it's very clear that they shouldn't be using this. Their use case is not complex and they haven't learned Rx yet. It’s one thing if you're like, "I'm learning this and I know that I'm going to have pain." That's totally acceptable. There’s plenty of times where it's like, "It's totally okay that there is no concrete deadline and I can take my time," but a lot of times and I would say, a majority of the time, you're slipping on the deadline and that's not a good thing. The people who are coming to me are just frustrated.
They’re like, "Why is this so complicated?" You're right, it is really complicated but its complication helps with some of the more complicated use cases. That's the irony behind both redux-saga and redux-observable is that they're both really complex for the trivial use cases like all I want to do is make an Ajax call. That’s it. I don't want to cancel it, debounce it, I won't do anything. I just want to make an Ajax call. They are the biggest hammer you could possibly think of. Don’t use them for that.
CHARLES: The thing is if you're not sure, whether you need redux-observable, then you actually don't need redux-observable.
JAY: I would say that usually that is the case, yes. The same with redux-saga. They’re in the exact same league. It’s basically, "Do you need complex side effect management?" With the redux-saga, the complications, even though generators have been around for a little while, a majority of people still are not familiar with them because they're just not used all that often. They’re kind of mystifying. I would say, they're not super hard to learn. They’re just alien. Then to add on top of that, the effect is data and you've got multiple curveballs. Same thing with redux-observable, they could never use observables. It's pretty alien. The reactive programming idea and model is pretty alien.
I would advise, if you're not using redux, don't even consider redux-saga and redux-observable personally. If you're already are using redux and you think that you might need something like this, experiment with the primitives, try and see how well your team can pick up RxJS just by itself. Just learn some of the regular RxJS tutorials, don't even look at the redux-observable docs because it's not useful in any way when it comes to this. Just try and learn a little bit Rx and see does it click. Because some people is like, "I don't understand why everyone thinks it's complicated. It's easy." But a majority of people, it takes a while before they get that like Neo in The Matrix, I-know-kung-fu moment.
CHARLES: All right. We're almost at time but I hope that that moment comes to everybody. I think we've certainly enjoyed a lot of success with it already and I think once you do get your head around the basic use cases and you know how to do an Ajax request, you know how to do just simple saves and gets and what have you, doing the trivial things becomes easy because you know which pathways to travel. But before we head out, is there anything that you've got coming up in the near future, any talks, appearances, meet ups, anything whether you or otherwise that people should be aware of?
JAY: I would say on this topic, there's a new beta for RxJS with this new way of using operators. It's being dubbed lettable operators, like the 'let' keyword but it's not related to that. It's basically just a way of finally being able to import operators and to use normal tree shaking that people have asked for forever. Because the problem with observables is that they're prototype-based methods and you can't reliably tree-shake methods or prototypes. We've been trying to experiment with ways to have [inaudible] and the lettable operator stuff is interesting to check out.
If you do that without this syntactic sugar, you've got this massive nested function invocation, which is incredibly hard to read and hard to maintain. That's why the pipeline operator is so great. I would encourage people, that's in beta right now. I think it's 5.5 but it's in beta right now. I encourage people to check it out, find bugs and get feedback. Maybe this is completely off base and it's not the right direction that the team should be going but it's based on a lot of collaboration, particularly with the Angular community. They've been, in particular asking for this because they're pretty big and they've got to use the Clojure compiler and all sorts of things for trying to make their bundles incredibly small.
For me personally, I don't have any Rx talks coming up. I've been pretty obsessed with web assembly here lately. I'm an armchair compiler nerd. I don't do compilers for a living but I have done them personally for a number of years so I'm obsessive with web assembly. I have number of talks in web assembly coming up but just nothing related to Rx at this point.
CHARLES: That's totally okay. I'm actually, also have been obsessed with web assembly.
JAY: Have you guys done a podcast on that yet?
CHARLES: I don't know.
ELRICK: No, not yet.
CHARLES: I actually started out to write my own list compiler in web assembly and they got totally derailed on the list compilers. Actually I ended up switching tracks on the whole web assembly thing but I was really, really excited about it. Probably, it was about three months ago or something like that but I'm still excited about it. I just haven't been working on it actively so I'm very curious to hear about those talks. Let's post them on the show notes and who knows? We do a lunch and learn every Friday here and usually, it's one of us getting up there but sometimes, we'll just watch a talk. One of us has been wanting to watch.
ELRICK: And you're always welcome to come back to any time and geek out with some web assembly.
CHARLES: I'd say, we haven't podcast web assembly, you know? All right, you guys, we've been at this for another hour. Let’s go. Everybody listening, strap your headphones on, we're going down for another hour. Changing the subjects: web assembly. It starts right now.
ELRICK: It's going down.
CHARLES: No, but we will have to have you on for that. Thank you so much for coming on and talking with us about observables, Rx, redux-observable but if folks want to continue the conversation with you, they can get in contact with you how? On Twitter, email?
JAY: The best way is going to be Twitter probably. I'm at @_JayPhelps. Thank you guys very much for having me on today. It was a blast. I love talking about this stuff.
ELRICK: Thank you.
CHARLES: Thank you and for anybody out there, we can also be reached at @TheFrontside on Twitter or just drop us a line and Contact@Frontside.io and have a great week and we'll see you next.