Transcript from the "Three-Dimensional Collections" Lesson
>> Jafar Husain: So one of the things you guys are going to get more comfortable with as we do this is dealing with multi-dimensional collections. In fact what you're going to be doing is you're going to be building many dimension collections and then applying the right number of flattens to flatten it back out into one.
[00:00:18] And then calling for each and that is what the exercise you're going to be doing today are going to be drilling again and again and again. I'm gonna keep mapping, keep building nested collections until I've got all the data that I need in scope through a closure. So notice I've got, well this is a bad example of that, but that's what you'll be doing.
[00:00:35] And then you'll be applying the right flattening strategy the right number of times to flatten it out into a nice flattened collection again. And then forEach over the result. That is not a skill that comes naturally to most people. It needs to be drilled, it needs to be repeated.
[00:00:48] But once you've got it, you're only gonna be learning five functions. And you're gonna get the hang of this way of programming. And you're gonna be able to apply it in a lot of places. Maybe unexpected places, places even beyond asynchronous programming. So I'm really excited about that.
[00:01:02] So I'm gonna go with over one example. We're just gonna bump it up to three dimensions now. So the Netflix player. Come on you can do this right let's take a look at this hornet's nest of code that I used to have. Right we saw this earlier this is the battle way of doing things right.
[00:01:17] We have a asynchronous program of the bunch of callbacks and a bunch of state and all that stuff. And we're gonna replace it with this. So what are the collections I have? What collection do I want? And how am I gonna to get from A to B? So what the collections I have are that somebody clicked play.
[00:01:36] Another collection is that somebody hits cancel. Another collection is that we successfully authorized a movie request. That's an observable of one that comes back from the server. Another collection is, I think that's it, actually. So, another collection is that the player is initialized. So the player could initialize asynchronously, we could asynchronously authorize a movie request, somebody could hit a cancel button asynchronously.
[00:01:59] I think those are the three ones. Yes. Those are the three collections. So, if I express all those as observables I can use these methods to compose them together into the collection I want, which is, movies to play. I as the UI developer just wanna create a nice little event that says, play this movie play this movie, play this movie.
[00:02:17] And then I'll forEach over it and I'll play the movie. So what I've created is a stream of authorized movie ID's. Movie ID's that were successfully authorized that some user tried to play and once they're authorized I actually want to go ahead and play them. So we start with player dot init.
[00:02:34] Player dot init just returns observable that fires a single value on next when it actually successfully initializes. Does that makes sense? Such an observable one just like thing that network across. I call player dot init and that could be back in observable and that observable actually technically when I call init it's gonna give me back an observable that does absolutely nothing.
[00:02:55] Nothing's happened. All I've done is create an observable, observables are lazy. So it's going to wait until I four each over that observable to actually try and initialize the player and then it's going to complete. After on nexting some sentinel value like true like I finished as soon as that player is initialized.
[00:03:09] Does that makes sense? So I call player dot init, it gives me an observable, not a damn thing has happened then I call for each on it. It actually then tries to initialize the player and once that player successfully initialized it's going to call on next give me some value like true and then immediately call on completed.
[00:03:27] Does that make sense? I'm gonna map over that observable. Notice it's an observable of one. I don't care. I'm still gonna map over it. And when I get that value of true, which I don't really care about, so my function accepts no arguments. I'm gonna then listen to all of the attempts to play.
[00:03:43] All of the user's attempts to play forever. So this actually gonna happened at startup I'm gonna do this, I'm gonna first listen for the player to start initializing and then all future attempts to play the movie, I'm gonna listen for this stream. That just means a click on the play button on the player.
[00:03:56] I'm just taken that click, that DOM event, and I've adapted into an observable, using observable frontend, so that's what player attempts are. I'm going to map over all those and that's just a stream of movie IDs that they try and click. So, if they try to click Die Hard what might come out is two, six, two, nine, five, they try to like House of Cards five, two, nine, one.
[00:04:13] Then I'm going to call player.authorize which is actually going to make a request to the server and just make sure that we can actually, you're allowed to play this movie. Cuz you're logged in and that type of thing, right. You're allowed to play the movie. And it's going to come back with a decryption key.
[00:04:28] So we're going to take that stream of play attempts of movie IDs and we're going to map it into a stream of actual decryption keys that we retrieve from the server. Does that make sense? That's just simple substitution. For every movie ID we substituted for a network request to get back a decryption key.
[00:04:49] So now how many levels deep are we? I think we're three levels deep. Because for every player initialization, even though there's only gonna be one. We're going to listen to all the play attempts, but for every play attempt we're going to issue a network request that'll actually resolve with the decryption key.
[00:05:03] So we're three levels deep. And how do I know that? Well, every single map function is returning another observable here. We have two map functions, each of which is returning another observable. So if the function paths to map returns another observable you know you're deepening the collection by one level.
[00:05:17] So we have a three dimensional observable and that means we need at least two flattening operations to flatten it back out into a one dimensional observable. So I always glance down there at the bottom and notice that there's two concatAlls. So we've built a three dimensional observable we applied to flattens to it and we ended up with a one dimensional observable.
[00:05:33] And then at the end we just listen to all the authorizations and we just play it now I know I've got the. So the authorizations is actually a stream of that license that you see there is actually the movie decryption key. So I'm just slurping up the movie decryption keys as they come along and then I'm saying the player go ahead now play this title.
>> Speaker 2: Two questions, one is cancels a custom stream that you made?
>> Jafar Husain: Yes, that's literally the cancel button or the back button, for example, that I've adapted using Observable.frontend.
>> Speaker 2: And then Observable.empty, what does that do?
>> Jafar Husain: Good question. So just like you can now observables error and so just like in in your language where you have things like try catch you want to be able to handle those errors.
[00:06:13] Right. If an observable errors you don't always wanna complete the stream immediately, they're just like in synchronous programming, you can sometimes catch errors and do something about them. In this particular case I'm always I just wanna listen for a play or authorization all the time. And if for whatever reason, some attempt to access the server errored, I don't want to make the whole stream of authorizations end.
[00:06:32] I basically, just wanna ignore it, right. In this case, I'm going to return nothing, meaning the play just won't work. So you hit play, that's probably not the best method, we could probably do even better than that, right, and be like, actually display them the message, but this is what I can fit on the slide.
[00:06:46] So what catch does It takes an error, excuse me, It passes an error to a function and that function can optionally return an observable to resume from. So instead of that error just completing the observable. Instead we can run that error through a function create another observable and the think of it is literally callcating it onto the current observable, and so we just start listening to that observable now.
[00:07:09] Which keeps the error from getting bubbled up, and stopping the overall stream.
>> Jafar Husain: So in this case, it's as if you did a catch, and then there was nothing in the body of the catch. And so an observable empty is literally an observable that just immediately uncompletes when you subscribe to it.
[00:07:25] So it's just a constant that will give you an observable that always completes. Right and but I'm now of course, actually bad example here. Down here I've got an error handler but that error handler, I don't believe will ever get get thrown because play attempts, no it could get thrown if the player initialization fails right.
[00:07:43] So, if an error happens on player initialization, there's nothing I can do so that error will bubble all the way up and they will handle it and say sorry you know can't play stuff right now.
>> Jafar Husain: Notice we're only catching errors on attempts to authorize a movie ticket.
[00:07:57] We're not catching errors on player initialization. Notice where the catch is it's very important. It's only on that inner observable, right, not on the outer observable. So if a player fails to initialize, we'll get an error. We won't catch that. So in Netflix [INAUDIBLE] we use observable everywhere.
[00:08:13] It's pretty much for every single asynchronous actions represented as observable. Yeah, question?
>> Speaker 3: An example on, can we have latest on switch play attempts?
>> Jafar Husain: Absolutely. I'm glad somebody pointed that out any time you see a, well actually, no no actually I'm glad we can't, I'm glad you pointed out because we can't.
[00:08:28] Just because we've got, notice like in the previous example we had like a take until inside of that map function, and a concat all on the outside. That worked because the incoming event that was creating observables, was the same as the event that we would want to use to switch to new observables, right.
[00:08:42] For every key press we created a new observable and then as soon I pressed another key we wanted to use that event to cancel the current observable in this case that's not true. We're listening to an entirely different event then so if you press play that event triggers us making us network requests.
[00:08:58] But it's entirely different event that causes us to stop making the network request which is cancel and that's why I had to use play attempts map returning the authorization observable. And then I had to do take until on cancels if I didn't take until on play attempts then this would be the same as switch latest.
[00:09:17] So switching this only works when you want to stop listening to an inner observable based on the same condition that causes you to get a new inner observable. So if every single time I press a key I'm gonna create a new network request, so I therefore want to cancel the current network request.
[00:09:32] So initially this is great. Because I can just you take until key press and the key presses are the same things creating the new observable. In this case I got two different events. One is creating observables and the other one is completing them. So that's why I can't use switch latest.
[00:09:44] I have to use map concatAll with a take and til inside. So for every observable I create, I say, yeah only stop when this other event happens. But whenever this incoming event happens, create a new observable. So I'm glad that question was asked. So switch latest only works when the incoming event that creates the observable, is the same event we want to use to complete but the current observable when a new one comes along.
[00:10:09] So a Netflix we use observable everywhere for every single asynchronous API every single asynchronous doesn't matter since a single value doesn't matter if it sends us a single value, doesn't matter if it sends us 15 values. Or whether it's a call back or it's a web socket or whatever we abstract it as an observable.
[00:10:22] The very first thing we do is abstract it as an observable and then we just treat everything like it's a collection and use these powerful data sources. I recommend you do the exact same thing. Any questions?
>> Speaker 3: Clarification why does a generate an empty observable?
>> Jafar Husain: Technically it generates an observable of one.
[00:10:41] So it fires one because if it had to generate an empty observable the map function would never get called. Right cuz the map function gets called for every item in the stream. So it actually just generates true and then completed that might seem a little weird but usually often things will do that just so that they can be used along with map because of an uncompleted them the whole stream would complete.
>> Jafar Husain: So that notice that map just ignores whatever comes inside of it doesn't really care all it cares that it got called that's how it knows that the player was initialized.