Transcript from the "Optimizing the Search" Lesson
>> Jafar Husain: Now, you mentioned earlier that you kinda thought that we would be using switchlatest for this, right? Notice, I'm using concatAll, instead of switchlatest. Well, that's actually more appropriate. I'm just aping what switchlatest does here, right, because with takeUntil and concatAll, I'm simulating what switchlatest does. Because every single time a new observable comes along, right, I've applied takeUntil to the event that causes a new observable to come along.
[00:00:26] So whenever a new observable comes along, the old observable is gonna complete and that's exactly what switchlatest does and that's exactly why we can simplify this to this. So, that's effectively what switchlatest does, every single new observable is just take until the next observable comes along. So, let's simplify this, to this and that is not bad in terms of code for an auto complete box.
[00:00:50] We've solved some pretty hard problems with very little code because we're thinking correctly about what asynchronous programs really are. We're thinking about them as streams of data that arrive over time. No different than an array. So retry, let's talk about retry. How many people you know what promises are?
[00:01:48] Promises cannot be cancelled. So, promises are obviously not the right, for those of you who don't know what a promises is, a promise is an object, which eventually resolves asynchronously to a value. Now a promise can resolve either with a value or it can reject with an error.
[00:02:04] It's a little bit like an observable that can only give you a single value. So you can call a ven method, which is sort of like for each. And you can give it two callbacks, instead of three callbacks. You only give it two callbacks, because a promise can only give you a single value.
[00:02:16] So there's no need for the uncompleted callback. As soon as it gives you one value you know you're done. The reason why we don't use promises for asynchronous network requests or for animations for that matter, is because you might need to cancel them, and a promise cannot be cancelled.
[00:02:29] So once you create a promise object, it's either gonna resolve or it's gonna reject, but there's no way of telling it, hey stop what you're doing. And that just doesn't make it very appropriate for most UI actions. Promises are useful for some things, like async actions that can't be cancelled effectively.
[00:02:44] But almost none of those things are what you do use when you use, almost none of the async APIs you use when building UIs, does not apply to, right, if you open a form and then something goes back you might want to cancel animations you want to unhook event handlers and so on and so forth.
[00:02:58] Right, you wanna clean up after yourself. With a promise, there's just no way of doing that. So, I don't think it's a very useful asynchronous primitive on the client. It's more useful on the server, than it is on the client, but it is from the masters and that's not why I'm not going to showing you promises.
[00:03:11] Observables are capable of doing everything a promise is capable of doing right? Observable will get you one value, it can push you 1000 values, right? The only difference is you can do more. And that's why I'm teaching you observables. It's more appropriate, because it's able with this one type to model not just async requests and animations, but events as well.
[00:03:28] And so you can take the average unit of work that a user interface does. And do it with a single type and it turns out to actually much more elegant. Okay. So, one of the things, because you can't cancel a promise and a promise is always a hot data source, so when you get a promise, it's already going.
[00:03:45] The network request is already going. That's not true necessarily as an observable. Remember the getJSON function we call here? When we get that observable out, you know what? We haven't issued a single network request. Creating the observable, doesn't issue the network request. What we're talking about here, is that example of the cold observable I talked about before.
[00:04:03] Nothing happens until you forEach over it. So when you get that observable out, it's not gonna do a thing until you forEach over, which the switchlatest operator will do right, when it gets the inner observable it forEaches over it. So it's at, that's a distinction between that and promises, when you call a function that returns a promise, the work is already happening.
[00:04:20] Does that makes sense? So, a promise, there's no way to retry a promise, a promise is a computation that's going right now. You can call the function again that gave you the promise and you can get it again, and that will retry the work, but with a promise itself, by itself, there's no way to retry an operation.
[00:04:38] That's because a promise, the work is already being done. With an observable, because it's forEach that triggers the action, you can call forEach on one observable three times, in this case the observable comes out in getJSON and triggers three network requests. So if you call forEach on this observable three times, you'll get three network requests, that might not be what you want, but that's what this particular function will do.
[00:05:02] So, well you probably don't wanna issue three network requests. If you call forEach concurrently three times, right. You'd rather have an issue one. And there's a way to do that, but by default this observable will actually issue three network requests. But now what happens if you get a network request and it fails?
[00:05:17] Well you can just call forEach all over again and trigger another retry to get that information. So forEach's lazy nature, which is another way of calling laziness. An observable is lazy. It doesn't necessarily do any work, because it doesn't need to until you call forEach on it. Because it's like a tree falling in the forest, with nobody around to hear it.
[00:05:37] Why should observable do work to produce values if nobody's listening yet? Does that make sense? So when you call forEach, that's when the observable starts doing work. And that's why we call it lazy. So in this case, if you call forEach on the getJSON observable, it will trigger a network request.
[00:05:52] And what retry does, is you give retry an observable and it calls forEach, and if it gets an On Air back, if On Air gets called, then it will increment a counter and call forEach again. Does that makes sense? And it'll just keep calling foreach until the counter gets to a certain level and then if it still gets an error, it'll forward the error along to whoever's listening.
[00:06:11] Does that makes sense? And so Network requests are a great example of something we almost always want to retry. Because intermittent network failures happen all the time. It's probably not a good idea to issue a network request and if it immediately errors just give up. Because server's not perfect, servers have problems.
[00:06:28] If you're scaled out, you might as well retry and try and hit another server, so for those and many other reasons. Observable is a better way of modeling asynchronous requests than promises, because A, you can cancel them, B, you can retry them and C, frankly, some network requests might stream data, right?
[00:06:43] They might stream multiple pieces of data and then observables obviously a better fit than that for promises which can only send you a single value. So we won't be talking very much about promises in front of masters. If you have questions about it, we can talk about it, but my recommendation is that you don't use them.
[00:06:58] And for UI development, for pervasive UI development, I don't think they're the right level of abstraction for most async problems. I think observable is the right level abstraction for most of the async problems using the user interface, so do we understand this slide? We're gonna see the same process happen again.
[00:07:17] We're gonna start with an event. We are gonna map it into an asynchronous request. Thus creating a two dimensional observable. And then we're gonna pick one of the three flattening strategies to flatten it out, based on what we want. So those three steps, we'll take the observables we have, compose them into the observable we want, those are three steps, right?
[00:07:35] And once we decide what these are we want is, and then will forEach over it and we'll consume the data and we'll do something with it. And that's that last step. I take the search result set and I put it on screen. I've done something with it. Any questions?
>> Speaker 2: Directed to you Bill, is retry injected to the prototype of functions to work on any functions we create?
>> Jafar Husain: Retry is not on a function, so that's difficult to see here. The fellow thinks that retry is on a function, it's not, it's on observable, getJSON returns an observable.
[00:08:06] So, retry is on the observables prototype. So, it's a method on observable. Cuz getJSON returns an observable. We're not calling retry on a function, we're calling it on the result of the function, which is an observable. Right. Because observables are lazy, you can write retry over an observable.
[00:08:22] Because it just keep calling forEeach and restarting the action. But if observable wasn't lazy, like a promise, you can't call retrial on it. Yeah. Question in the back?
>> Speaker 2: If we use switchlatest, why do we use the throttle, is it through authorization?
>> Jafar Husain: No the throttle is what turns abc, def, into cf.
[00:08:42] That's to keep us from sending many many network requests. But I see the question he's asking. That's actually a great question. If we remove this throttle right here, we would actually get the exact same result. So this it's a very perceptive question from the because he's looking at this and saying like, well, why are you doing this?
[00:08:59] Because, every single time we get a new key press, we're just gonna cancel the outgoing network request anyway, which is, all we've done with throttle is we've just eliminated a few keys before we issue a network request. So throttle is actually, purely, a performance optimization in this case.
[00:09:12] Because, we've dropped a and b, right? Later on, let's say we didn't drop a and b, and d and e. And we just actually let abcdef all the way through. What we still would have just created are a bunch of network requests and then cancelled them. And so by filtering out a and b from a, b, c, d, e, f, and d and e from a, b, c, d, e, f.
[00:09:33] Which avoid creating network requests that we're gonna turn around and cancel them immediately anyways. So it's actually cheaper because we don't create the observable. So that's a great question. It's actually purely a performance opposition. Yes.
>> Speaker 3: So does it cancel the request or does it just not listen to the result?
>> Jafar Husain: Great question. Well it's really all up to the observable, right? If she says, look I don't want any more cake right? I'm the producer, I might be able to pick up the phone and call the cake makers and say like, stop the order, stop making cake. I don't want any more.
[00:10:02] Or, I could just leave the phone on the hook and when the cake comes just throw in the garbage. It's totally up to me. Now most observables, understandably, well written ones would wanna do the former and not the latter. And so in practice, yes, that is what getJSON does.
[00:10:16] It actually goes to the XHR and calls abort.
>> Speaker 3: Okay.
>> Jafar Husain: So that may or may not actually stop the network request from going out. But at the very minimum, it'll stop you from getting called with the results.
>> Speaker 3: Okay.
>> Jafar Husain: Right? But, very often in fact, you will be able to stop the network request from going out, especially in cases like this, where you've got a lot of inputs coming in because what is gonna happen is the browser will build a queue of outgoing requests.
[00:10:35] So, it's not that you issued a request already, it's that it's actually awaiting to issue a request.
>> Jafar Husain: Yep.
>> Speaker 2: Can you use retry to pull a request?
>> Jafar Husain: To pull a request?
>> Speaker 2: To poll.
>> Jafar Husain: You wouldn't use retry to poll. Although retry is a lot like poll, because you keep making a request again and again and again, right?
[00:10:56] But retry is specifically just for that one behavior of making sequential requests and only continuing as long as you're getting errors. Up until a certain point. We will learn how to poll, though. It is perfectly possible to do a poll, by creating an observable, that fires an item every hundred milliseconds or something.
[00:11:13] Like an interval, you said a set interval. So you can do a set interval, which is just you give the browser a callback and it just fires that callback again every hundred milliseconds. Or whatever interval you define. You can also create an observable. Just on next to you every hundred milliseconds, and then you can take that and map that into a network request, and now you've got a poll, right, now you've got an input observable that's just gonna fire every hundred milliseconds.
[00:11:35] If you map each one of those items that it delivers in on next, it might be just null or true or something, into an observable that makes a network request. Now you've actually got a poll, because every hundred milliseconds you make another request. Yeah?
>> Speaker 2: Question, are you gonna engage the internals of getJSON later?
>> Jafar Husain: Yes, but not right now. For now it's just, you take it on faith that under the hood it's using XML HTTP request and it's gonna onNext you the result and onComplete immediately. Yeah?
>> Speaker 2: Then, one more question, SwitchLatest Is switching between multiple observables in this example, is map returning multiple observables?
>> Jafar Husain: Well no, map always takes an observable and takes every item, well depends what he means by multiple observables. Map is just a very simple mechanical function that takes every item in the collection, runs it through a function and it's effectively substituting that item for a new item into a new collection.
[00:12:29] So like number 1, 2, 3, if I apply a plus 1 function to 1, 2, and 3, I get 2, 3, 4 into a new collection. Here in the case of map, the function is actually returning another collection. And so, we're going from a one dimensional collection to a two dimensional collection cuz the map functions returning yet another collection.
[00:12:45] So we go from 1, 2, 3 to one array of 1 comma, array of 2 comma, array of 3 effectively, so that's what we're gonna find again and again, we're gonna see the same pattern, we're gonna take events. We're gonna to map them into a synchronous requests, creating a two dimensional collection.
[00:13:00] And then we're gonna pick the right flattening pattern to flatten them out again, and once we've flattened our observable, then and only then will we call forEach, because calling forEach over two dimensional observable is not a lot of fun. It's really weird, right? It's hard. Who wants to have to deal with this inner and outer observable stuff?
[00:13:17] You let the flattening operators do that work for you, and then you only have to worry about a nice flat observable of results. Does that make sense?
>> Speaker 2: Where are the multiple observables coming from?
>> Jafar Husain: For every key press right? Let's look what happens for every key press we've run it through map, and getJSON returns an observable.
[00:13:39] And so that means for every key press object we create a new observable. So that's where the multiple observables are coming from, before we just had a stream of key event objects and now after the map operation, we have a stream of observables, we have an observable of observables, because the map function is taking each one of those keypresses and converting into another observable.
>> Speaker 2: Is that inner collection returned by map, does that only have one element?
>> Jafar Husain: In this instance, yes, it's observable of one because getJSON just makes a network request, takes whatever comes back from the network and then on next it and then immediately on complete. So, yes it only contains one element.
[00:14:17] So we're creating an observable of observables of one.