RxJS 5 Thinking Reactively | Ben Lesh

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
BEN: Hey, everybody. I will start out a question, with a question here: are you new to RxJS? When you're using it? Do you feel lost? Helpless? Alone? I'm Ben Lesh. I'm here to help. I'm excited about this. Actually, a lot of people know me from the web, and they might not believe I'm Ben Lesh, so it's me! It's really me. I brought the hat. I don't always wear the hat. I do at conferences, otherwise people don't know who I am! So how long have you been using RxJS? Who, here, has been using it for longer than a month? Keep your hands up. Longer than six months? Longer than a year? Oh. It's going down! Longer than two years! Is there even one? Where are you! Stand up, all right. That's awesome. How about longer than five years? That's not even me, right? I've been using it for a little more than two years. More or less. That surprises some people, I suppose, but that's true. But RxJS is more than nine years old. It was created by a developer a long time as as part of Microsoft Project Volta was targeted compiling C# to JavaScript and wanted to. Not long ago, I was pretty new to this. Questions I was asked when I was new to this, the first question I asked what is this? Another low dash. It looked like low dash to me at first blush. When I did get into it, I started asking this question all the time: what operator do you I use for that? How do I do this the Rx way, right? Why did that observable die? I put a catch in there. It shouldn't be dead now. It should just keep going, right? What is this Rx code I wrote four months ago? These are questions I asked. I'm going to answer a few of the of these, that is what this talk is about. Usually, when I go to conference, I have to explain what an observable is to folks. Here, the Angular people have been really great about promoting reactive programming, including it in Angular 2. If you're sitting here and you still don't know what observables are, I will try to explain it but there are plenty of other resources out there, and even around this conference to learn exactly what an observable is. We are talking about thinking reactively. You know what an observable is, and you want to start using them. It's hard to kind of wrap your head around it when you're used to imperative programming. You need to start viewing your events in terms of dependencies. So you look at something, for example, like a drag and drop. A drag and drop is if you are going to write out what it does, for each mouse down do some target, you start listening to mouse movements on the documents until a single mouse up on the document. When you look through what you're doing here, you can identify three different sets of events that we have to compose together. These are our kind of dependencies to our one sort of event that we are trying to build - we are trying to build a single drag-and-drop event. I'm going to start off, and we are going to select a DOM element. This isn't Angular-specific. We will select a DOM element and create observables from a mouse-down on our target. We will create one mouse movement from our document, and one from mouse ups on our document. These don't do anything until you subscribing to them, they're observables. Basically setting up functions here. We go back and we look, and we say, "What are we doing inside of this?" In this highlighted section here, we say we want to compose mouse movements on the document until a single mouse up. I can do that really easy with Rx, so that's just mouse moves, take until mouse up. Again, this is just setting up an observable. It does not do anything until you subscribe to it. Now we've basically got this kind of function that is going to set up an event stream of mouse movements until a mouse up on the document. For each mouse download targeting, you start listening to that thing we've just made, so, what we can do, is we can say, okay, for our target mouse downs, I'm going to switch map because I've got this other asynchronous thing I want to start, and I'm going just to plug in that previous piece that I made, that mouse movement takeUntil mouse-ups. That's drag-and-drop. That's sort of the process that you have to think about when you're trying to think your way through these reactive things. It's kind of backward, but you have to identify your different streams of events and then look at your problems to see how to compose them together. So, more thinking reactively. Another way to think about observable is when you're looking at your imperative code and think how do I convert this into something observable? Maybe you're trying to write something new, and you want to write it from scratch, and observables, but you really only know how to do this imperatively. You can go into your code base, and you can find any line of code and examine the variables. What you need to know is that given one of those variables is observable, right? Right here, we are saying we want to call or do something with C, so, if we had a stream of C, we could subscribing to it and do something every time it nexts, right? C comes from adding A and B. If we had streams of A and B, we could combine them and add them together. With combine latest. If you're just looking at this arbitrary section of code, one thing to be aware of is there is probably something that caused either A or B or both of them to change which is why you need to add them together again and doSomething again. You need to work backwards and how do I get a stream of As? You need to think with your goal, work your way backwards, and recognise where you're getting these streams of information from in composing them together. So, here's another one I get with people who start out, which is, "Oh, my God, RxJS is so confusing." People are more comfortable with promises or something. They're really, really stressed out about what operator to use, so confusing. If you find it confusing or just starting out, stop worrying about the operators. Seriously. Stop worrying with the operators. Like people get very excited about all these tools you just handed them, and it's scary to them. You're probably comfortable with promises before. A promise here on the top, you've got then and it takes a success function and an error function. Observable, you've got subscribing, a next, an error, and a complete. It's really not that much different. One has more characters, I suppose - it's got another argument, right? But, in all honesty, let's face it, you're probably only using the first argument in most of your thens, and the first argument in most of your subscribes, so that's not much different. If you still think it's different, I will change the names for you. Quick draw - both using callback. Now, I don't know which one was which from before! But basically, without operators, observables are no scarier than prom sells. You can start using observables right now and if the operators are freaking you out, subscribing to them, do imperative programming after that. That's how I started. I have didn't know all the operators. Maybe you can get your feet wet with map. That's a good starting point if you don't know what to do - maybe, might be too scary. Which brings us to the next question that I get, what operator do I use? I want to build all these things, and it's going to be amazing, and they're going to be so reactive. What operator do I use? People get jarred about that. Remain calm - it is okay! So, the agony, agonising over what operator we want to deal with. You can use the operator guide where our docs are. There is a really, really good guided process that helps you pick what operator you should be using at the bottom of the page. Developed by Andre Staltz - good stuff. You don't have to Rx everything. People are trying to overuse RxJS. Just take the operator that you know, and get them up to the point where you can get them, and then subscribing and do the rest imperatively, if that's what you have to do. It's totally fine. This is so everyone sees, this is a screen shot, if you do go to reactive.io RxJS, that's where you will find the choose your own adventure operator finder thing - it's pretty cool. I recommend starting with these operators. I work on a lot of apps at Netflix, and I help people on the internet, and friends, that use RxJS, and especially when I have experience porting, you know, Rx 4 and 2 over to RxJS 5. I get a real first-hand look like at what operators people have and what they're using. These are the common ones - there are a few more that come up, time, if people do coincide rate-limiting, things like that. These are the ones that people end up using a lot. So there are 60-plus operators, start with these and work your way up from there. You will be a little more comfortable. This is where I'm going to dive into some deeper technical stuff about what an observable is doing. I want to try to demystify observables a little bit. People look at observables, and they're like, "What is happening inside this? There is this class, it is doing async things, and it must be unicorns and dark magic in there. There are schedulers and all this stuff." People get freaked out about what an observable is really doing. Some of that might be coming from promises, because promises do have forced async behaviour and nuance where it is sketching errors and things like that. What if I told you that observable is nothing but a function? I know I've said this already a little bit in my talk, but it's really true. To prove it to you, I'm going to show you what it is like to build an observable that is just a function. If an observable is just a function, it would be a function in a just took an observer, right? That's all it would do, it is just a function that accepts an observer - easy-peasy. It's going to call a few methods on that observer. Here I'm setting up an interval, and I'm nexting out - technically 11 values in there. Ten values, actually. And then I'm calling complete when that is done. And the other thing it does is it returns some teardown logic. In this case, it is returning a function that clears the interval that I can call at a later point to tear it down. Whenever I call the function, I call it with an observer, and the observer is just a plain object with a next air error and complete method on it. You just pass that in. That is your own subscribe, if you will. So, you get that teardown back, and later, you can unsubscribe with that. This is where our teardown comes out. We're calling it at a later point in a set timeout in this case. To move on from that, operators are also just functions. Operators are function that is take an observable and return a new observable. In this case, it is functions for returning the same observable, but it is still a operator. A bit like a noOperator. Anyway, we know that an observable is a function that takes an observer. We can replace an observable with a function that takes an observer, and, inside of that what we are going actually to do is an operator is going to subscribe to the source, that source observable, with its own observe year, and then it is going to take the observer it was passed and actually end up calling that from inside its own observer.s at that taking an other why wrapping it with its own observer so it can do something. We will make this a map operator. In order to make this a map operator, all I really did was I renamed it "map" for one and then passed it a mapping function. I'm calling that mapping function before the next destination observer, right? So there are two observers inside here. There is the one of that object-literal that I passing to, and the other one that I'm passing values to, and I'm also calling a map function. If we use it with our observable we created earlier, it would look like this. We are passing our observable to it in a mapping function, and then we are going to go ahead and subscribe to it, log it out, and it will next out these values. It's pretty cool. We've got one mapping in there. What is it going to look like if we chain them? All right, we've got map, and then another called a map with my observable in it. Two functions in there. What happens? It comes out, with it's actually chaining, getting the exclamation point and question mark in there back to back. What happens if we want to do a lot of operators? Let's see, we've got an observable, we are scanning it, and then we are filtering it, mapping it a couple of times. That's pretty gross. It's really not cool. Maybe we could do code for mat to go make this better? No, still gross! So I think it would be nice if we could do dot-chaining which enables us to have this much easier-to-read format. Let's wrap our observable functions in a class so we can have dot-chaining. We are going to have this class called observable and have the constructor accept the observable function we were making before and we are going to set it as a property on our class, and then whenever we want to invoke our observable, we just - we actually just call this observable function on our observable instance, but that's not the best name in the world. We could probably change that to subscribe. Now we can add operators to the class, and when we add operators to the class, and the operators are also returning a new instance of the same class, that means whatever they return has the same operator, and you can chain them together. Right? Now it is much, much more readable. I didn't actually change that much from the code this this map operator that's in here, the code for it is almost identical to what I showed you earlier. I refactored it a little bit so it could be a member on this class. So, when I run this, I get my map chain executes, if it's much, much prettier to read. So observable as a class provides dot-chaining. The ability to add safety to the type, so, one of the things that observable does inside of itself is that it makes sure that you have passed. If you've only passed a next handler, it's going to rough in error handler and completion handler for you to do some default behaviours. It's also going to kind of wrap your observer and make sure there are guarantees like you can't call next after it's completed, and you can't call error after it's completed, and so on. You can't called next after it's errored, and all these things. It provides some performance optimisations, and in Rx, we can look at properties in the class and make decisions about what we want to do with it in certain operators. Scaler values can be processed a little quicker that way. But really, really, all that it's actually there to do is wrap that observable function. It's just a function that takes an observer. Think of observables as functions. Don't think of them as anything else. They're not doing anything when you've been handed them. They're not running anything, they're just functions. So, functions again, don't do anything until they're called. The observables are lazy. That's another word you hear a lot, that you have to subscribe to it before it will do anything. Operators take your observable and return it into an observable. Operators return an observable, and an observable doesn't do anything. Calling "map" isn't going to execute anything in your observable. That's a misunderstanding IBA seen a few times as well. I want you to notice something else. This is my map operator from before. So, if you look at this map operator, you see that I'm returning a function, I'm returning an observable, but we can think of it as a function, and, inside of it, it's creating an observer, what is in white, and passing valuation to another observer which is in green. It is chaining these observers together. It is when you call the function, it's basically saying, "I'm going to set up a whole bunch of observers that are all talking to each other." That's an observable chain. Right? So, it's just a function that connects chains of observers, so you've got a safe observer at the top where you're producing your data. You've got one for your filth, yes, one for your map at the bottom, your wrapped observer, so, in this case, it would take your next handler and wrap it in a server. It is chaining them together just like you saw before. So let's actually visualise this. I've got this observable interval. In my observable interval, after that, I'm going to filter it, then go and map it, and subscribe, log it out, whatever I'm going to do with those values. These rectangles here are represent observers, and I have one for my producer at the head. The other thing to note is that each one of those represents a different notification channel. My next channel, my error channel and my complete channel, those horizontal lines going across. I have one at the head, one for my filters, one for my wrap, and then finally one that wraps my handlers. If I'm stepping through this, the producer next handler zero at the filter observer. The filter observer passes for zero when it runs that assertion on it. Then it nexts along to map and sent along, 0+0 is 0. Next we're going to send along 1. It goes to the filter and does not pass. If it doesn't pass, it's the it's not going to be sent along. That's it. So what about error-handling? Error-handling, this is probably one of the bigger points of confusion for people, so I'm going to spend a bit of time on that. A thing to note about observers, they actually have rules. Observers will no longer pass along values if error has been called, if complete has been called, or if you have unsubscribed. At that point, the observer will be closed. There is actually a - it will no longer allow you to next out things or error. What does that mean for error-handling? Let's look at this contrived example. We've got an interval again. In my interval, I'm going to throw an error whenever I hit the value 1. So, first it is end zero and that goes along. We are map it, and we're not doing anything with it which makes it to our subscription. The next time we sent along one, and 1 throws in our map. So the observer right then and there, that particular observer is marked as closed because error has been called on it. Nothing can pass through it at that point. You can't next on it, can't error, you can't complete, which means that everything upstream from it is gone. It can't send anything through any more. And then the error is signalled down the chain, and after that, you're done. So, to handle an error, we have the catch operator, and, just to explain how this works, it takes a function that gives you the error and you're expected to return an observable, that it's going to use in place of the observable that just failed. So it's very similar to promise-catch in its structure, but what it is doing under the hood is a little different because we are doing an observable, so, using catch on the same sort of example, this time I've got an observable of just the value 1, and map is going to throw because it hates 1, but I don't know who writes code like this - but I do! We're going to catch that and return the observable 2. What happens here is we send the 1. Map fails because it hates 1s, for whatever reason, and we send the error to the error-handler on catch. Well, the catch observer just had its error-handler called so it is now closed. What it's going to do is it's going to say, "Hey, let's actually map this to this new observable of 2." And so it creates a new observer to subscribe to that observable at 2. We set up our error channels again, and then we signal along 22 to the next handler, and then, after that, because it's an of, it's going to complete right afterwards which means that one is now closed and done, and we're all done. So what is there is catch is saying, "Yeah, this died up here, but I will provide this other observable for you to finish out your process." But catch, you know, it is still let our whole observable die. It's upstream from it. It's now gone. When you run into certain situation, you don't really want that. How do you solve that problem? How do you keep your observable from dying when you catch an error? Again, this is a very common question. You isolate your observer chains. So we now are setting up these chains of observers whenever we are dealing with observables. So let's talk about the interval you don't want to die. In this case, we are doing something where we are polling. Every ten seconds, we will make an Ajax call, and, if it fails, which can very well could, we don't want it to die. This is what people try at the beginning, and this going to work and I will show you why. At first, we send along zero and we make our Ajax request which starts off a brand new observable. It's going to wire things up to signal back into our switch-map observer, and what is going to happen is when it merges the value back in, that's going to be passed through because we will say this first Ajax request was successful, and it's going to go all the way through, it gets passed through catch because catch doesn't care about next handling, right to our next handler. The next time we are going to send one, and this time, I'm going to say this this Ajax observable is going to die, so this observable, we set it up, we subscribed to it, and it's got errors. If it has errors, then both of these have had their error-handlers called, they're closed. You can't send anything through them any more. That means that everything upstream from that, including the interval, is now dead. Well, at the very least, can't send anything through. So what do we do? The catch observer is like, "Okay, it's the it's closed, and we will replace that with an empty because that's what it mapped to." Empty completes right away, it doesn't have any value. And the whole thing is done. Oops. That's not good. How do you keep an interval alive? Countless - countless emails! [LAUGHTER]. Let's solve this. What if we put the catch inside of the switch map this? Let's see what happens. I'm going to jump right to the error scenario here. The interval fires; the switch map creates the Ajax observable; and this time with a catch: so now we've got this observable chain sitting off to the side, and the Ajax is going to error, and that error is going to go to our catch. Our catch is going to map to a new observable of empty, and empty completes, but switch map has this behaviour where it says, "Hey, my source isn't complete yet, so I don't really care if this child observable is complete, I'm going to keep going." While that one dies, the external, the main observable chain, the main observer chain, stays alive. So, when the interval fires again, switch map fires, create another Ajax - you get the idea. So what happened? We created a second observer chain. Whenever you have an error, your observer chain dies, so if you can isolate your observer chain, you won't have a problem, particularly if you punctuate that second observer chain with a catch or a retry, or something that prevents that error from being pushed back down into the main observer, or the main observer chain. So that is going to shield our main observer chain. That's what we want. The tl;dr of this is if you're having a - merge maps, switch map, what have you, unless it's okay for your observable to die, in which ways, handle it where you choose. Another thing that I see when I'm helping people is they will end up with some memory leaks or something like that: subscription management mistakes. This is very common, and that's usually comes from failing to unsubscribe. So failing to unsubscribe will cause memory leaks, resource leaks of various sorts, because unsubscription is what tears down the resources you set up when you subscribe to begin with, so think about like add event listener, and remove event listener. I think many of us have set up an event listener and forgotten to use it and don't know why it is using a whole gig on Chrome - that's amazing. So this is one way to manage your subscription. Inevitably, you will have to do this with Rx, managing imperatively. You will have to create a subscription and later on unsubscribe. People end up wanting to do this everywhere, and they end up with something like this. Now, when you look at this, I mean, who here can spot the one that I missed? Yeah, four was missing. It's really easy when I line them up like that. But what is it like when all that stuff is mixed into all the rest of your code. Right, when it is sprinkled throughout this complex application, it becomes very, very easy to miss something and cause leaks. So there is another way to do this: you can manage mostly declaratively. You still have to have a subscription in there somewhere. In order to manage declaratively, you can do things like "take until". I've got these kill observables coming up from up here, coming interest from a stream of route changes, one like that, one is a subject, and that basically means that you can kind of take all your observables, merge them into one, so you only have one subscription and use takeUntil to take them until you want to. You could add a do block to handle the subscribe logic individual for each one of those streams if you wanted if you didn't want to handle it in your subscribe later on. But there are a lot of ways to handle this declarative subscription managing - they all behave similarly. There is switch and switch map which have an inherent unsubscription behaviour built into them. So, the good sides of declarative subscription management is you have fewer subscriptions to manage so you're less likely to miss one of those and forget to unsubscribe. The other interesting thing is you can use any event stream to trigger an unsubscribe. If you have a stream like this is to kill my Ajax call, whatever, you can do whatever you want to make sure that still happens. And, again, you can compose this any number of ways. I mean anything that can be an observable, you can use to kill your other observables. So there is another approach to a subscription management, though, let your framework or libraries handle it for you. A lot of libraries and frameworks have a handling for RxJS observables built in. You can see the pipe async where you're right out this observable. So just some studio code example here. If you have a template here, you can write out foo$ with the pipe async, as you have the observer interval, as it updates, it will cause your view to update. There is a gotcha to this that people don't realise. The gotcha is that, right here, I'm going to log out when the timer's fired. The timer, like everybody knows, is a set timeout. It will fire one time and give us a value and that's it. Most people would expect only to see timer fired logged out one time. When I run this, I see it fired twice. And this is people like, "I don't know why this is happening twice." Let's say it is an Ajax call or something underneath that, you've made the Ajax call twice, people get bent out of shape like that and don't like that. Why is that? Because you've created two subscriptions, and, as I've stated for most of this talk, observables are just functions. They don't have any state or anything, they're just kind of, "I'm going to go ahead and I'm going to set up this whole observer chain every time you subscribe to me all the way up to the top." So it does that twice up the top, it sets up the timer twice in this case, and the timer fires twice. So, here's how we fix that. We fix that by adding a share to the end of our observable chain. What that is going to do is that is going to multicast. It basically means that it's going to wait for the first thing to subscribe to it, that will set up the observer, the observer chain, and then the next guy that subscribes to it, that's going to get wired into that same observer chain. When that observer chain fires, it is going to broadcast to everybody that's listening. But fair warning: multicasting comes with costs. So the costs around multicasting is you're going to end up allocating a subject, you're going to end up allocating an array to stuff all your servers in, you will end up allocating a couple more subscriptions you don't want to multicast all over the place. If you find yourself doing share, share, share, share, take a step back and examine what you're doing and why. Pipe A sync has pros which is no subscription management at all, which is really good. The other thing that is really cool is you've got this automatic unsubscription when something is removed from your view. Let's say like you have a web socket or something feeding into some data and you had it set up so your observable closed the web socket when you unsubscribed, that means as soon as you - if something out of your view, the web socket will automatically close. So it is really a cool way to consume observables. The con to this is that it can encourage the overuse of multicasting, so people using share all over the place, and that can impact performance. But a good subscription rule-of-thumb that I use is more than two is probably too many. If I find myself managing three, four, five observables, and single component or a single module, then managing that many subscriptions rather, in a single module, I think to myself, this is probably gone overboard on this, should use a more declarative approach. All right, so this is the final point that I'm going to make here, and the very first talk I ever did for the Angular community was at ngConf a couple of years ago. I contradict myself, don't Rx all the things! You can build your app as one big observable, but please don't. I've seen this. I have a lot of friends in the Rx community, and I have had some really brilliant people come up to me, "Ben, check this out! I made a whole code and it's one observable. You can subscribe to it, and it does everything." I'm like, "Really, I would like to see that. There are probably five people in the world that can read this!" [LAUGHTER]. Rx is a domain-specific language. It takes a while to learn it. Once you learn it, it is extremely powerful, it makes your code we readable, but you should Rx where it's best suited. So, it's best suited for doing things like multiple events together, like drag-and-drop. I've done other things where I have had to do brush selection on a graph where you click and you select like a little range or whatever. Be things like that, it works really, really well for because it is very easy to modify those sorts of things and alter it declaratively. It's good for adding delay, or client-side rate limiting, maybe setting a timeout, things that involve adding times to your events. It's really good for co-ordinating async tasks. If you have a bunch of things happening asynchronously and you want to make sure when they come in, they come in together, or that your you're co-ordinating various things, maybe web sockets and workers, whatever, it is really, really good for that. It's also great when cancellation is required. If you have Ajax requests you want to be able to cancel or abort like I talked about, most of my other talks, it's, they're fantastic for that. However, do not use Rx when it is not needed. If you've got simple button clicks, you don't need an observable to add an event listener to a button. That gets silly. You don't need an observable to wrap everything in your form and update something so that you can submit it later. "Hello, World" apps do not need Rx, right? I can tell you for a fact, and I'm sad to say I've actually seen that. Thinking reactively, just to recap: when you think reactively, you kind of want to work backwards. You think about your goal and you think about what all of your event streams that you're going to read that goal, to build together like Lego pieces to get to that goal. Any variable can be an observable. Anything that you look at, just, again, imagine that code executes once in a while over time, and that variable changes. That's a value that changes over time. It can be represented as an observable. Observables are just functions. There's no deep magic inside of them. Don't be scared to use functions every day. You will be fine. But observables get too much credited. Observer chains do all the work. An observable sets up this chain of observers and then it goes away. It's just a function you called and it's done. There are templates to set up these observer chains. Everybody talks about observables. Really, it is the observers that matter. Calling error actually will break that chain. So that is an important thing, observers have rules. If you error, complete, unsubscribe, that observer in your chain is done. And, just remember your subscription management rules. Two or three or more is probably too many subscriptions in a single component in my opinion. If you like that sort of thing, go nuts, make sure to have tests around that, though. Then only use Rx where it makes sense. I love Rx. I think it is really cool when people show me stuff, like that one big observable, it's so much fun to get that this stuff. That's why I'm in it. You know, don't make your team hate you! Don't go out and be like, "Oh, our whole app is in here now, it is great!" Your team will not like you, even if they think this person really knows their Rx, I would recommend against that. That's it. Thank you very much. [APPLAUSE].
Info
Channel: AngularConnect
Views: 75,288
Rating: 4.9161777 out of 5
Keywords:
Id: 3LKMwkuK0ZE
Channel Id: undefined
Length: 39min 4sec (2344 seconds)
Published: Sat Oct 01 2016
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.