Check out a free preview of the full Asynchronous Programming in JavaScript (with Rx.js Observables) course

The "Observables in Action" Lesson is part of the full, Asynchronous Programming in JavaScript (with Rx.js Observables) course featured in this preview video. Here's what you'd learn in this lesson:

Jafar demonstrates how Observables would work with the functions he introduced earlier. He runs examples using map(), filter(), and concatAll() and talks about the results.


Transcript from the "Observables in Action" Lesson

>> [MUSIC]

>> Jafar Husain: What is definitely not coming in JavaScript 6, is a special syntax for creating an observable. I am going to invent a special syntax for creating an observable, just like we have a special syntax for creating an array with the square brackets. Let's imagine, we have a special syntax for creating observables which are collections that arrive over time.

The only reason I'm gonna invent that, so it's easier to understand how observables work and how the functions that we're gonna be learning about, map, filter, and all transform observables. So definitely not coming in JavaScript 6, I've just invented it for this particular presentation. But this is the syntax that I'm gonna use for a collection that arise over time.

We're gonna imagine that we have a future version of the JavaScript language, and we can invoke methods on this literal for describing an observable, okay. So let's say I have this observable, imagine each one of these dots is like 10 milliseconds for example, right. And so if you were two forEach over this observable, you would immediately get called with 1, and then a little bit later I push 2 to you, and then a little bit later I push 3 through to you and then I'd say onCompleted.

Does that makes sense?. That's the callbacks that you would see. So let's see what happens when I call forEach on this. In real life, the observer you're gonna be learning about, has the forEach method which we saw to find earlier on mouse moves, right. Does this exact same thing as you would expect when you did it over array.

The only difference is, it takes time. It arrives over time. Remember when we did this for example with an array, it finished almost instantly. And now it's actually just whenever the data comes in from the underlying source. Then it gets translated and then immediately handed off. Question in the back.

>> Speaker 2: They're asking about it every forEach call is returning a different dispose object?
>> Jafar Husain: Yes.
>> Speaker 2: How do we hold all of them and use them?
>> Jafar Husain: Well, we call forEach right, you're gonna get multiple values, and again, that shouldn't be too hard to think about because it's just the same as addEventListener or removeEventListener.

When you call addEventListener, you have to remember to hold on to the handler because that handler is the same handler you have to pass back when you call removeEventListener. Here it's no different. Instead of holding on to a handler, you have to hold on to the subscription object.

So when you call forEach, you're gonna get many, many values. And at some point you may decide you don't want those values anymore and you have to call subscription.suppose. That's no harder than calling remove event listener and passing in the same handler you're holding on to before. So one shouldn't be any more complex than the other, all right?

Same complexity for-
>> Speaker 2: I think they might have been thinking about, so, every object that comes back, the dispose will just turn off the whole subscription, right?
>> Jafar Husain: Yeah.
>> Speaker 2: Yeah.
>> Jafar Husain: Yeah, so you're not gonna get any more data. So here's what happens when you call forEach over an observable.

Remember map? Map applies a function to every item in a collection and creates a new collection containing all of the transformed results. That was true of an array and it's true over an observable. If we call map over an array. What we get is an observable that pumps us out each value but first transforms it through the function.

So we take one 1,2, 3 and we apply a function that adds 1 to each item and we get 2, 3, 4, but notice it arrives over time. As soon as one comes along, an observable doesn't hold on to data. As soon as a value comes along, it's gonna immediately transform it and then send it along.

We don't collect up data in the memory, as soon as data arrives, we translate it by throwing through that function and then we hand it off like a hot potato. That makes sense? So we mapping. Now, let's try filtering. Imagine, same thing as before. We're gonna filter, we're gonna take the observable and we call filter on it.

We create a new observable that contains only those items that pass the test function. And so we took an observed look and then 1, 2, 3 and we got an observable that came 2, 3. Yep?
>> Speaker 3: Should that plus be a greater than?
>> Jafar Husain: Yes it should. Thank you.

[LAUGH] Live code reviews. Sorry. Yeah. Question in the back.
>> Speaker 2: Does forEach take this arg, should I even worry about it?
>> Jafar Husain: forEach does take this arg. Probably not a super important point at the moment, but yes it has the same interface as the array forEach. Yeah.
>> Speaker 4: So, map was returning an array, right?

And now it's returning singular elements, or-
>> Jafar Husain: So, sorry, this slide can be a little confusing. What's actually happening here is I'm shortening the slide from this to this. So, I'm creating a new array but I'm also calling forEach on it so that you see the values that come out inside of it.

So in this case I'm actually creating an observable and we call map. All you've done is create a new observable. And then the forEach in here, it's kind of implicit. I'm just putting in a forEach to show you what would come out of the console if you were to subscribe to that observable.

But that's a good question.
>> Speaker 4: And that's why at the beginning that's where you're running the concat all because we have all these mini arrays.
>> Jafar Husain: Yeah, we have a bunch of mini arrays which we don't wanna print that out to the console, right.
>> Speaker 4: You're clever. Okay, l'm with you.

>> Jafar Husain: So I don't wanna cheat, so I'm gonna put that in here. So we can all see what's going on. I might go off the slide a little bit.
>> Jafar Husain: So let's take another look at that, right? So .map actually just returns an observable. And so when we see stuff printed out of the console, it's because we're .forEach-ing over that observable.

So we get 2, 3, and then eventually 4. Right, because that's how long it takes for the items to arrive. And as soon as the items arrive, they're translated and then they're forwarded on. Hot Potato. Same thing with filter, we're gonna get 2 or 3. We skip over the first element, we never return it because it's 1.

And now I want to call your attention to concatAll. So this is over arrays, you remember how concatAll works over arrays, right. Now how many different ways really is there to flatten a two-dimensional array. I suggest there's really only one sane way to do it, which is to start with the first array, go through all those elements, move to the next array, go through all those elements, move to the last array, eventually go through all those elements and then put all of that into a new array.

And that's effectively what concatAll does. It starts top to bottom and goes left to right. So it goes top to bottom left to right. So here, this example, we get 2, 3. Excuse me. We get 2, 3, 4. 1, 2, 3, 4. That's the only way that you can flatten the two dimensional array.

It's only way that make sense. Here's where things get more complicated. An Observable is in a collection over time. And it turns out that if you have a collection, an Observable of Observables, there's more than one way to flatten it. Because the items arrive over time. And there's three strategies.

In fact, you can solve almost every asynchronous concurrency problem where you've got more than one thing going at once? Almost every concurrency problem that you're gonna run into the UI with three basic flattening strategies for an observable of observables. So it's really key that you just get these three down.

And you'll start to see that many, many problems that look brand new are very different, can actually be classified into one of these three flattening strategies. So we have concatAll. So what does that mean when applied to an array? Or excuse me, what does that mean when we apply to an observable of observables.

So what this is, is an observable where if you're foreached over the outer observable, a certain amount of time would go by, and then eventually you get another observable and presumably you'd foreach over that, right. But in the meantime the outer observable could give you another observable while you're still not completed with that observable that you've just got, right.

Because the observable happens over time. And so you have this potential for concurrency now because in an observable of observables, you've got this new dimension of time. Which we didn't have on array, right. All the data is there instantly at the same time. So with an array we just exhaust things top to bottom, left to right.

So here's what concatAll does, the concatAll strategy would do when applied to an observable. So let's say I foreach over the outer observable here. The observable of observables. First, I would get an observable. In this case, let's just say just contains one. And then I'm immediately gonna foreach over that.

And then what we do is we take the data, and we put it into the flattened stream. So we see 1 appear immediately. So now it's completed and just as it completes. The outer observable gives me another observable. This one contains 2 and 3. And so I immediately forEach over that, that inner observable.

2 pops out, I immediately push that out into the output stream, nice, flat output stream that comes out of concatAll. Now while I'm waiting for that second observable to complete, the outer observable throws me another observable. This one's empty. So what concatALL does, is one thing is, I could begin forEach-ing over this observable immediately.

But I don't. The what distinguishes concatAll from the other strategies. Is it always make sure that elements come out in the order of it that the collection that they were inside of arrived. So here I really wanna see 1, 2, 3, 4. So we're not actually gonna forEach over this observable.

Yet I'm just gonna put it away, I'm gonna hold on to it but I'm not gonna call for each on it. Does that makes sense? So now I'm still just waiting for 3 to arrive and while I'm waiting for 3 to arrive, that second observable to complete, another observable comes to me.

Once again I'm not gonna forEach over it, I'm just gonna put it aside. The key thing to understand is, in this particular example, all of these observables do not start emitting values until you for Each over them. Does that make sense? All of these observables do not start emitting their values until you forEach over them.

So if I don't call forEach on these observables, data is not gonna come out. Now not all observables behave that way. It's possible that you could have an observable that is emitting values, regardless of whether you are calling forEach over it or not. Can anybody think of a like a data stream that will emit values.

And if you don't start listening to it, soon enough, you might miss some values. We showed you one earlier, mouse moves. Right, let's say I'm moving my mouse around and you haven't hooked up an event listener. You're not gonna get those event objects, right. You've missed them effectively.

You only start getting those event objects when you add your event listener. We call that a hot observable. It's a hot data source. It's going whether you're listening or not. What I'm showing you in these slides is an example of a cold observable. A cold observable will not do anything until somebody calls forEach on it, it will not do a single thing.

And so the data inside of these observables is not gonna come out until I call forEach on this. So all these slides use cold observables. So that's why, even though the time has passed where I could have emitted 2, or excuse me, I could have emitted 4 here.

It hasn't been emitted yet because I haven't called forEach on it. So now I'm still waiting on that second observable to complete. And then I actually got two observables in a buffer somewhere but I haven't called forEach on yet, does that makes sense? It's that third and fourth observable.

Now finally that second observable completes give me 3 and completes, and says on completed. So on next 3, on completed. Now I immediately subscribe to that third observable that I've been keeping over here in a buffer somewhere. What happens to this empty collection in the middle of the flattening operation?

What happens to empty collections when they get flattened? They disappear because there is nothing returned. Yeah?
>> Speaker 5: We know it stays empty because it's already fired into onCompleted.
>> Jafar Husain: I guess, if you forEach over an empty collection, the only thing will happen is it fires onCompleted. That's how we know it's empty.

Yeah, question in the back.
>> Speaker 2: Question, yep, in the empty array, 5 was in there, that wouldn't happen until after the 3.
>> Jafar Husain: That's correct, yes. So if instead of an empty array. There was like 5, you would end up, the outputted collection would be 1, 2, 3, 5, 4.

Because it always returns elements in the order based on the collection that they're inside of arrives. So, basically what you need to know about observable concatAll is it behaves exactly the same as array concatAll. It goes top to bottom, left to right. So here in this case, we're gonna subscribe to this observable and basically it's like a no-op, nothing happens, because all empty collections, just when they get flattened they just disappear.

Now after we get the onCompleted message from the empty collection. We forEach over this other collection that we've been keeping to the side and it calls onNext gives us 4, and then immediately calls onCompleted. And so we return 4. Notice what happens with concatAll, the output stream ends up kind of getting elongated.

It's longer than any of the input streams, right, because we basically have to make sure to enforce that things come out in the right order. And that means we need to buffer things, right, buffer observables, and make sure to only forEach over them when the previous observables have completed.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now