André shows us how to build a minimum functioning RxJS and teaches us some important concepts about reactive programming.
JavaScript has multiple APIs that use callback functions that all do nearly the same thing with slight variations. Event listeners, array methods such as .forEach
, promises, and NodeJS streams all are very close in the way they are written. Instead, in RxJS you'd unify all of these APIs under one abstraction.
Toy Rx is an interesting library to go beyond this livestream.
Joel Hooks: [00:00] I build. This is "Build your own RxJS." Somebody told me that you had a talk that was amazing that was about this topic. I thought like, "This is just a great way to learn these kinds of things, build it yourself." My favorite example of it is Dan's Redux course on Egghead where he goes and he builds Redux. You're like, "Oh, OK, well, you know, it's 30 lines of code. Now we have this working thing."
[00:25] Not that you should necessarily build your own RxJS. If that's what you're into, go for it. That's like an exercise. It's a learning exercise. This is a great way to do it. I am looking forward to it. We have people, if you are watching and you're in the chat, I want to encourage you to ask questions. That's basically what I am here for. I'll restate what André says. I try to ask dumb questions, André.
[00:50] That's really what I try to do. I try to think about, "Oh, I don't know." I don't know any of these. I've used Rx a little bit but I am no expert. I would say John's used it quite a lot. He is here to provide that kind of color commentary. If you have questions, there's a Q&A feature in Zoom so that you can see the button at the bottom of your video screen where it says Q&A.
[01:14] That works pretty good. That's easier than trying to monitor chat. We'll be doing both. Feel free to speak up and ask questions. There are no dumb questions. We're all here to learn. That's it. Otherwise, André, I am just going to...Do you have anything to add, John?
John Lindquist: [01:32] Nothing for me, no.
Joel: [01:33] No. I'll just let you take it away. Feel free to share your screen.
André Staltz: [01:38] I had a throat ache last week so my voice sounds a bit lower than usual. I can still hear me. I probably sound even better than usual. Right in here, we have a file that is using the normal RxJS, it's requiring the observable from RxJS, and a couple of operators. I have a very simple observable here. It looks a bit big but it just emits the numbers 10, 20, 30, 40, and then it completes.
[02:11] I pass this observable through some operations such as I divide the numbers by 10, 1, 2, 3, 4. Then I filter so that I don't have the number two. We can consume this last observable. I am just explaining what I have here. This is like a normal use case of RxJS. This works, but I have...
Joel: [02:40] I have a question. Can you tell us what an observable is?
André: [02:46] An observable, I'm probably going to explain it better later, but I can basically explain it as an abstraction that goes beyond a promise. If you think of a promise, it resolves or rejects, and it gives you just one value, like "Fetch request from the server," and then it gives a response or it gives an error. That's a promise.
[03:14] Observables go beyond. They allow you to emit many values over time, so you could have streams of data from the server or you could have numbers coming from some source. You can have a stream of DOM events, such as clicks. It's an abstraction just like a promise is an abstraction. Functions are abstractions. Also, observables are abstractions and they're quite useful for building things.
[03:46] I'll probably give better examples later but that's in a nutshell what an observable is. It's like a stream. My point here is that I'm just showing a small example of using RxJS, and once I run this I'll pass all of those numbers through these operations. At the end if we run this code we're going to see I think it was 1, 3, 4, and done. One of the operations was delay, so just literally make things slower for one second.
[04:24] Over here, I have another file which looks exactly the same, except for the imports. Here I'm importing an observable from my own Rx. I built an own Rx here. I'm importing the operators, and the usage is exactly the same, as well as the behavior. If we run this, we're going to see exactly the same behavior. It says 1, 3, 4, and done.
[04:53] You might ask yourself, "Why use the real RxJS if you can build your own Rx?" As Joel hinted in the beginning, of course you're going to use the real RxJS, but it's really useful to demystify RxJS. Once you build it from scratch, you actually learn a lot of things and the magic sort of goes away.
[05:16] That's a very important process, because sometimes we use these things and we just feel confused about some details, but we try to ignore those details and just keep on going. That's dangerous, because at some point, we may get into very tricky bugs. That's the point of building our own RxJS.
[05:34] I can just tease to you what this file looks like. I'm not going to explain that file, because we're going to build it ourselves. It's over here, and it's something like 90 lines of code. As you can see, it's not that much. It does everything that Rx did in that example. We're going to get there, just slowly, little by little.
[06:02] I want to explain a little bit better why RxJS. There are all kinds of callbacks and events in JavaScript, such about basic click that you can listen to. Here, you always have this API in the DOM where you addEventListener. Here's the special function. You pass a function that will be the recipient for all those events. Callbacks are usually used for asynchronous stuff, such as clicks.
[06:37] It's not true that callbacks are only for asynchronous programming. There's also callbacks for synchronous code. Here's an example. I have an array of numbers, and I can call forEach. I pass to it a function. In the same way, this function is a recipient for all of the numbers. If you look at the API, array.forEach, this is just a name. What if we would have named this addEventListener? It's just a name.
[07:11] In that sense, it doesn't look that much different from the clicks. This is just to say that there's all kinds of APIs in JavaScript that use this kind of style where you pass a function. Another one would be the common promise. Here, I have some URL that has some data on some users. I call fetch on that URL, and it returns to me a promise. Then I can call this method .then.
[07:44] I can pass it not one function, but two functions. Basically, I have two recipients here. I have the recipient for normal data that we got from the server, and we have error cases. Still, it looks quite similar to addEventListener, because you're listening to the event of successful response. You also have this other case here, in case things go wrong.
[08:18] I'm just trying to point out the similarity between all these different APIs in JavaScript. We have yet one more case, which would be if you're using Node.js. We have streams in Node.js, so you can create a readable stream of, let's say, file system-related stuff. Then you can register three recipients. Let's say the function to get data, that's one of the recipients. Then in case of errors, that's another recipient.
[08:49] Then finally, when the stream is closed. Let's say we finally read everything that there was in this file or whatever, then this function will be called. As I said, this could have been implemented with slightly different names. Maybe if this would be addEventListener, or if this would be .then. It's just interesting to note that there's a lot of these cases where you pass a function in JavaScript.
[09:20] The way that we use these things is slightly different. As you'll see, we have on, we have then, we have forEach, and we have addEventListener. All of these are different, but in essence, they're not different. That's the interesting thing. What observable in RxJS does is that they say all of these things should have the same API, same way of -- I'm sorry, I punched my microphone -- consuming, same way of creating.
Joel: [09:53] It's a unified abstraction. We have all these different abstractions. They all look the same. At the end of the day, they add a lot of, I think, cognitive overhead, because we have to think about all these different abstractions. "I'm using a stream now. OK, now, I have to think about streams. I'm using a callback and a forEach. I'm using an event listener."
[10:35] Where in reality, they're all solving the same problem, but in slightly different ways for different preferences. With Rx and the idea of observables, we can combine all these things into one.
André: [10:49] Yeah.
John: [10:49] If I can point out, I think, like the addEventListener returns undefined, forEach returns undefined. A promise would a, or then would return a promise. All those are so different. I'm sure you'll get into it, the teardown phase of telling these to stop. You would have to do a .removeEventListener, keep a reference to the function, and all that sort of stuff.
[11:19] As a unified abstraction, it makes that teardown part of it, it's unified as well, for each of these scenarios.
André: [11:28] That's a very good point. Sometimes, because you might need to remove an event listener, with promises, you don't have that. You can't cancel promises. Just this shows that there's a lot of variation out there, even though essentially these APIs look so similar that they could have been one API. That's what we're going to do with RxJS.
[11:53] I have an empty file here, and the rough pattern that we see in all of these four examples was that we have some kind of object, like I'm just going to call it OBJ. Inside that object, we have some kind of method. Sometimes, it's called then. Sometimes, it's called addEventListener. I'm just going to call it giveMeData this time.
[12:17] We know that it's a function that takes a function, that recipient that I was talking about, or in other words, callback. This is the rough format that we usually have. In the case of promise, we had two callbacks. We had the data callback, and we had the error callback. In the case of the Node stream, we also had the done callback. I'm going to put all those three here.
[12:49] Once you have all of these three, you can generalize. For instance, if I'm getting clicks, if my object is the element, and giveMeData is addEventListener, then I just need to pass one callback. Of course, you can't fail a click. Either you do the click or you don't. You can't declare to the user that there's no more clicks going to come. Just imagine that we would have addEventListener. You add three callbacks.
[13:23] Let's say a callback for errors, but this never happens, you can't fail a click, and a callback for done, but this also never happens. As you'll see, if we have these three cases, next, error, and complete, we can cover all of these other cases, because we can just ignore error and complete when they're not relevant. You see?
[13:50] Back to this example. This is the most generic case that we can cover. One, two, three, and four. Let me just try and improve my voice a little bit here. Sorry for that. In the case of, let's say, array data, I have some arrays, some array that has numbers. I'm going to consume it through giveMeData, not through forEach. Then it would look roughly like this.
[14:23] I would have my array inside of here with the numbers there. Then you can imagine that giveMeData is another word for forEach. Then we can just do a forLet over these elements in the array. Then we can pass that to the data callback, like that. This might be a little bit hard to see, but I can compare these side-by-side, and then it's going to be quite obvious.
[15:01] This is what we had before. That looks very much like array forEach function, X. You see, there's the same shape, it's just different names. This example should work. If we run a 05 like that, then we get 10, 20, 30, 40, as if I did array.forEach. Instead, I did object, giveMeData. Of course, I could do this also for a promise. I could wrap that promise situation also in an API that looks like this giveMeData thing.
[15:45] Let's make another file for that. I had this promise there. I'm going to wrap it in an object that has giveMeData. Then there's three callbacks. There's the next callback, there's the error callback, and there's the done callback. Sometimes, we call this data callback or next callback. Anyway, it's just a name. Then I can put all of this inside here.
[16:16] Once you say giveMeData, I'm just going to call .then, and I'm going to pass these two callbacks. You see? Like that. Then I can consume this by saying giveMeData, and once I get the response from the server, I'm going to, let's say, console.log that response.
[16:41] In case I get an error from the server, then I'll just console.error, let's say. This is looking a lot like the promise.then, with function X and function error. You see? It's the same shape, so object and .something. Of course, I could do this also for the readable stream in Node.js, but you're getting the idea here.
[17:11] All of these other APIs, we just wrap them literally in this other API that's very generic, and it's the same for everything. I will just use better names this time, because giveMeData is not that great. Just to give you the idea that this could have been any name that you wish, it actually could be called forEach, or it could be called sometimes subscribe.
[17:37] This is starting to look familiar to RxJS, because RxJS has a function called subscribe. We can start recognizing some parts here. Basically, if you can subscribe to something, then something is subscribable. It's something that can be subscribed to. One of the possible names that they could have given to the observable would be a subscribable.
[18:08] This would be a totally valid and theoretically reasonable name to give. They thought just of giving it the name observable instead. It's just a naming issue. These objects are called observables. Any object that has a subscribe is essentially an observable, if it follows all these conventions.
[18:32] Then I have observable.subscribe. Of course, if I'm observing something that is observable, there is the observer. What is the observer? It's essentially the consumer of the observable. We have the producer, observable, and you have the consumer, the observer.
[18:58] The observer is basically this part. Previously, I said recipients. Essentially, the observer is the recipients, so these callback functions. It's like a group of three callbacks. Also, one of the nice things I could do is bundle all of these three callbacks into one thing, just grouping them into one object.
[19:22] That's what I'm going to call the observer. The observer is an object with three callbacks, or three functions. Here, we have next, which is this thing here. We have error, which is that other recipient, and we have complete, which will probably not be that much relevant. I could change this API here so that instead of passing three functions, I can pass the observer object.
[20:00] Now, here, I'm getting not three callbacks, but an object with three callbacks. It's just a slightly different way of doing it. Now, I have all those three callbacks attached to this object. I can just pass them over here. At this point, I think I'll just ask, does anyone have any questions at this point? Is everything clear? If it's going too fast, then it's good to stop. If I'm going too slow, how does it sound?
Joel: [20:32] Sounds good so far.
John: [20:34] How closely is this related to the observer pattern that we've read about in programming before?
André: [20:41] The observer pattern from object-oriented books is quite similar to this, but the observer pattern talks about subject and observer. I would say there are some bridges between RxJS and that pattern, but it's probably best to say that RxJS is the observer pattern re-imagined, or basically...
Joel: [21:12] You could say the observer pattern, you see it a lot in addEventListener or promises, too, right?
André: [21:19] Yeah.
Joel: [21:20] If we trace this back, then the observer pattern is the basis, but this is the encompassing abstraction. I think this is, it feels to me like a similar, like the observer pattern. Maybe not specifically with the names the Gang of Four put out or whatever.
André: [21:35] Exactly. It depends how religious you want to be with the official observer pattern. Definitely, if you're abstracting over all of these mentions of observer pattern, then definitely, this is just observer pattern, but not religiously the observer pattern from the Gang of Four.
John: [21:56] These abstractions, they weren't even written in JavaScript first, right? RxJS started as Rx-something else.
André: [22:04] RxJS, actually, it was Rx. It started in C#. Then it was coded, I think, actually the second one was probably RxJS.
John: [22:14] Was it? OK. It's just really cool how this abstraction works across languages.
André: [22:20] Yeah, because we're talking about functions, right? Almost all the languages have functions or something like that, or methods. It works quite well for all of these things. It's nice to know that you can create a common API for JavaScript, all of these JavaScript APIs. It's also cool that it covers Java, C, C#, and whatever. It's awesome. That's the really awesome part.
Joel: [22:49] It's more like a way of thinking than, like the implementation detail of a specific language. It's like we can think this way instead.
André: [22:58] Yeah. A detail that probably everybody missed here is that I'm using from the observer object, just next and error. I'm sorry, I'm jumping back to the code. I'm just looking at it, and I can't stop talking. We're not using complete, but we do know that when the promise resolves, we're done. The promise won't do anything anymore, ever.
[23:31] For that reason, we can create a small other function here called a nextAndComplete. This one will call observer.next, and it will call observer.complete. This is just in order to use all of the three, next, error, and complete. Because we know that after this promise is resolved, nothing else will happen, so why can't we just tell the observer that we're fine, we're done?
[24:11] This will become more useful later. Of course, this still works. If you only send next, it works, but sending nextAndComplete will become useful later for combining all sorts of observables. This is literally just wrapping the promise code into an object called observableWithSubscribe.
[24:35] That's cool. It just doesn't sound so useful. I'm just wrapping stuff up. It's just an API. That's true. The observable is just an API that wraps things and standardizes everything. That would be boring if we just stopped there, but there's much more beyond that.
[24:59] I want to demo another part of RxJS. I'm just going to write this example, not for promises, but for arrays, because promises are a bit easier to demo. I'm going to create a new file here. I'm going to use arrays instead of promises. We have our shape here. As you'll see, this is a pretty generic shape. I have something that can be observed, and I have the observer, which does some console.log stuff.
[25:33] The observer is going to subscribe to the observable. Here, I can pass to that observer that bundle of callbacks. I can call the next callback a couple of times with, let's say, array data. I could say 10, 20, 30, 40, and forEach. I can call observer, observer.next. As you see again, I'm wrapping this forEach API in the subscribe API.
[26:08] After I have sent all of the numbers, I can also call observer.complete, just because I know for sure that nothing else will come after this. Let's run this example, just to be sure that things are working as we expect them to. There we go. The numbers are delivered to the observer. As I said, now, this would be boring if this would be everything, but let's add the so-called operators.
[26:37] Operators are essentially a way of making transformation to this data without literally going deep into the observable and getting that data. More, you take one step back, and you just say, "I want data that is transformed, but I don't want to go into the details and do the transformation." It'll become a bit more clear once I demo this. Essentially, with arrays, you have operators, just like we're going to demo.
[27:17] We have the map operator, for instance. You can have one array here, and then you can have another array that is array one, map. Then you can do stuff with those numbers. There's different ways I could do this whole operation.
[27:37] As you can see, map allows us to take one step back and just say, "Please run this thing over all of the numbers. I don't want to do it myself, detail-by-detail, but I just want you to run this operation on all of those numbers," which is this calculation here. We can say, for instance, X*3. Then array two is probably going to be, we console.log what array two is. This is just to be sure that, just a sanity check.
[28:13] We got there 9, 15, 21, 27. Our goal is, what if we have map also for observable? Now that I wrapped by array in an observable, I don't have .map anymore, and that's sad. I lost the map. I had observable object there, and it doesn't have .map times three. This is going to break, and that's sad. We can actually build map. It's really not that hard. I say that just to prepare yourself to see something easy.
[28:53] We can write it. Actually, yeah, before I write it, I'm going to actually show how we're going to use it. I'm just going to say, observable two is map of, let's say, the operation that we want, X*10. Then I'm going to pass observable one, essentially. I'll just call this observable one. Oh, I'm sorry. Yeah, well, it didn't want to rename it for me.
[29:34] There's different ways I could do this. I could this with dot notation or with a second argument here, like observable one, and the thing. I'm just going to use this API for now, because it will end up being the same thing as what RxJS uses, which is the pipe method. Just to make things match with RxJS, but definitely, X10. Essentially, I want you to think that this is really not different than array.map, X10.
[30:12] It's just instead of dot, we have this kind of function, this extra function. Now that that's clear, let's go ahead and try to write this. Actually, let me move this down here. It's a bit easier. Map will take this F function. It's this smaller function here, this arrow function that you see. It will return another function. We know that it returns some kind of other function.
[30:50] This other function has observable1 as input. I'm going to just write there, input observable. Once I call this whole thing, it returns another observable. It's a function that takes this input observable and return an output observable. In case it's hard to understand, what is an observable, just try to think, better array. It's a better array. Input better array and output better array.
[31:24] We just need to build this output observable. How do we do that? Observables are, as we saw here, objects with subscribe. We just need to make an object with subscribe inside it. That's how easy it is. Subscribe is a function that takes an observer, which we could call our output observer because this object is the output observable. This is the observer for that output observer.
[32:04] Now, OK, we need to get data now. How do we get data? If you remember previously, I had written another word for subscribe was, giveMeData. We can get that data from input observable. We know that that's essentially the only thing that an observable has, is this function, giveMeData, so this subscribe function. Now, we know how to subscribe to stuff.
[32:36] We just pass it a bundle of callbacks, the observer. We just pass those three callbacks. I'll write them later. I'm just making a template like this. This looks a little bit like a soup, but take a deep breath. This is actually not that hard. What we're doing here is, from this input observable, I want to get the data. Once I get the data, I want to transform it with this F function.
[33:08] Let's say I get an X. I'm going to transform it with the F I call F of X, I get out the Y. For instance, if X is 10, then 10 applied to this function will give me 100, so Y is 100. Now, I built Y, and I can pass Y forwards. I have the way of passing it forwards, because here, I have the output observer. I just say output observer, next. Here's your Y number. OK, that's it. Now, we just need to think, what about the errors?
[33:48] In case the input observable has an error, or it blows up, let's say that this one blows up, then of course, we'd want that one to blow up, so that the errors just keep on propagating until they reach their final destination. That's a rather easy case to cover. We just say output observer, error, error. That's it. Once we get an error from the input observable, we're just going to pass it onwards.
[34:23] Similarly, once the input observable is done, we can just say, oh, yeah, the other one is also going to be done. This actually should work. Actually, I think this should work. If this one was 10, 20, 30, 40, and we multiply it by 10, we're going to get 100, 200, 300, 400. I hope that's what's going to show up in the end of the console. Zero-seven, there we go, just like that. That is the map operator.
[35:05] We could have a better API for this, actually. Just like RxJS has .pipe, so I don't know if you remember exactly. I can show here in the first file. Observables in RxJS always have this .pipe. Then you say map with that transformation function. Let's do the similar pipe. It's actually much easier than it sounds. It's actually such a cute function to write. We just say pipe.
[35:40] Our observable is going to have to have a subscribe method, but also a pipe method. It takes as input an operator, which is a function. This one here from input observable to output. We need to pass this object into this function. That's pretty easy. The this object is literally the keyword this. The operator there, this is supposed to be a function. I just call operator on top of this.
[36:19] This object here is the this keyword. I pass this whole object inside to this function, I get out another observable. That's my output observable. I just return it. Let's see that actually works. Instead of this style, let's do observable2 equals observable1.pipe. Map X to X*10. Does that work? Let's take a look. It does. It's nice to see that this is not a mysterious function. It's just a simple juggling it around.
[37:08] Now, if you've been watching this very, very carefully, you might notice that observable2 has subscribe, but we don't have a pipe on that one, actually. If I wanted to do another map, let's say I wanted to map this again, I do pipe map. Let's say X => X minus 9. This would actually blow up, because when I created this observable here, this object, I missed the pipe.
[37:45] It would be silly to implement pipe every time you just do this. There's actually a smarter way of doing this. We can just create a helper function. We're going to name it createObservable. We can write it over here, createObservable. I'm sorry, it's a function. What is this going to return? It's going to return an object that has subscribe and pipe. We just need to implement these two.
[38:20] How are we going to do that? The contents of subscribe are different for each observable, but the contents of pipe are the same for every observable. I know I can just have this one implemented over there. It's always going to look the same, but the subscribe is going to look different. It depends on how our observable is. We actually need to pass it as an argument here.
[38:51] This is a function that we just want to attach it to this object. This object also has pipe. Then we can easily get rid of some repetition. We can just say createObservable, and here's my subscribe function. I can get rid of that one. See if I format the code. Oops, sorry. Format document, OK. I just passed my subscribe and create the observable. It's just going to attach it to an object that has also pipe.
[39:39] This is just a helper method. I could also use this helper method for the other one down there, which was missing the pipe. I could do, instead of this object here, I'm going to call createObservable. I'm going to pass the subscribe function. I think that should be enough. As you see, I just made a helper function so that it always attaches the pipe, as well as the subscribe.
[40:12] Now, I think this is looking better, because I think I can also do an additional pipe map here. Let's say X minus 9. Let's see if that one works, and it does. This is getting nice, so I'm going to do a little bit of shuffling around, because as you see, observable2.pipe, this would return observable3.
[40:42] We don't actually need to name all of these intermediate observables. Instead of calling this observable2, we can just pipe all of these operations together like that. You can forget about the intermediate names, observable2 and 3.
[41:05] Of course, you can name them if you want, but it's not necessary. We know that once you call this, you're going to get out observable2. Once you call this, you get out observable3. All of these are objects that have subscribe and pipe. We're getting somewhere. This is starting to look like RxJS. If you take a look at the first file, it had an observable, it had pipe, and it had some operators.
[41:33] You could also build other operators, of course. Filter is quite common. It exists also on arrays, so we should also have it for observables. Gladly, this time, we can just copy-paste the map, because it looks quite similar, the map and filter. I'm just going to call this one filter. Now, this F function is a condition.
[41:57] For instance, let's do like this. I don't want to see the number 200, for some reason. I don't like it. I'm going to check that the X is not 200. This F is a function that checks now, is it true or false, and if this condition is true, then I let it continue. If it's false, then I just don't do anything.
[42:28] That's the case here. When I get that number from the input observable, I'm going to check with that F function. If F of X is true, then I can send it forwards. I'll put the observer.nextX, that same number. Then if it's false, then we just don't do anything. I can actually remove this else. This should work. Let's take a look if it does what we think it does, and it does. We have 100, 300, 400, and done.
[43:06] You can see, now it's just a matter of starting to build your operators. That's pretty much it. There's a lot of operators in RxJS, but if you look under the hood, they all follow the same idea, that there's an input observable that serves as the base for the output observable. There's the input observer, the output observer, but it's just setting up phase. Essentially, this is a good template for RxJS.
John: [43:48] If I can just interject.
André: [43:51] Sure, go ahead.
John: [43:53] It took me forever to realize that RxJS is essentially a library for wrapping callbacks, or these bundle of callbacks.
André: [44:02] That's a great description.
John: [44:04] When you think of a lodash or an underscore, and you think that's a library for wrapping the arrays and the objects, but when you get into asynchronous in the callbacks, and you want to add other asynchronous options, whether it's delay, it's filtering or mapping, or whatever, RxJS just provides you this abstraction around callbacks. That's why they don't have callbacks.
[44:27] Exactly what you're showing here is, I think, illustrates it beautifully.
André: [44:34] As you see, we are using map and filter, and those existed in arrays. One may be thinking, "What is the point of wrapping with all of these things if arrays already had those?" Here's the catch is that we don't have map and filter for click listeners. We don't have map and filter for promises and streams.
[44:58] Now, if we wrap these in observables in a similar way that we just did with arrays, suddenly, we get for free map and filter. They were conceptually really the same style. Let's do, just as an example, click listeners. I'm going to create...
John: [45:18] Sorry, if I could say one more thing. Also, when I was first learning it, I would look at that subscribe method, and I would read everywhere that subscribe is the way that an observable starts. It tells the observable to go. I never really thought of it beyond that. I'm just passing a callback, or this collection of callbacks, into subscribe.
[45:38] Again, it took my brain forever to realize, wow, these callbacks are being passed all the way up, and being decorated and decorated and decorated and decorated.
André: [45:47] Oh, yeah.
John: [45:49] [inaudible] . That's where the magic happens. It's not the fact that the values start at the top, you have observable, and then go through the operators. It's that that final callback you pass into subscribe gets decorated by all the operators. Then the values go through.
André: [46:09] Yeah, actually, that's a really good thing to demo, for instance. We could add just a bunch of console.logs here and see what happens. For instance, we know that things start in observable1. We can add here console.log, getArrayData. We can call observable1, and then observable1...Or let's call this initialize, subscribe, or whatever. We just say, yeah. Next X, and then we say complete here.
[46:57] Just as an intermediate step, I can show you how this looks like. Once we run this code, it says subscribe, and then it says next, 10. Then it passes through some operations. It says next 20. It doesn't show in the final observer. What's going on here is that, whenever we do an operator like a .map or something, we are literally just creating a new object, when you think about it.
[47:27] For instance, if I comment this subscribe, basically, observable1 will become observable2 and then observable3. This is the output. Oh, sorry. The output here is observable3. I can show you what happens if you run it like this. There's no subscribe. Let's just run this again. There's nothing that happens. This code did not get called at all. That might sound odd, because sometimes, when you do...
[48:06] You can also console.log the observable3, and it's going to tell you something not so interesting. It's just an object with subscribe -- there you go -- and pipe. What's happening here is that these steps are actually not calculating anything. There's literally no calculations happening in this program, unless we have this line uncommented. This kickstarts everything, but why?
[48:36] If you follow this through, this subscribe here is the output of filter. We know that it was literally this function. It's this function. This subscribe is literally this function. When that happens, the first thing that happens is input observable.subscribe. This one, this function here, is literally the output of map. We know that in this case, which is literally this function.
[49:09] Then you see again, the first line here is subscribe to the observable above. This .subscribe is literally this function. As you'll see, there's an initial step where the lower one says subscribe, and then the other one says, "Oh, I'm going to subscribe above, above, above." Then finally, this one is ready to start giving data to the gatherers below.
[49:37] It's going to call observer.next, which in this case, is literally this function. There you go. X is 10, and then it passes it through the transformation, and it gets 100. It passes to .next, and this method is literally this function. As you'll see, it's starting to bubble down. Finally, it will bubble to the output observer, which goes to the console.log. It's really like a chain of go up and go down.
[50:08] One way of thinking about this is that, if I ask you, what's the time right now? If you don't have a watch on yourself, you might ask someone else, "Hey, what is the time?" Then they may tell you to it's 12 o'clock, and then you tell me back. It's like a chain. I request you something, you don't know. You need to ask someone else. In between, you could even translate to Spanish, you see?
[50:41] I could ask you in Spanish, "What is the time right now?" You could ask someone else in English, and you'll see that transformation step of the translation is similar to what's going on here with map. That's translating to the observer below. It's the same type of processes going on. Does that answer a little bit how the subscribing magic kickstarts?
Joel: [51:12] Yeah, as someone points out, it's closures wrapping closures that are all kicked off with inside-out with subscribe.
André: [51:19] Of cause, all of this is very naïve. The actual RxJS does much more. It does some things called scheduling so that you wouldn't have literally a stack of functions but sometimes you may store one of those functions in a queue like an array that then gets called latter, maybe with a set time out, who knows, or requests automation frame.
[51:48] All of those things are possible. You don't need to do them recursively. You don't need to wrap all these closures inside. One of the beauties of asynchronous code of callback code is that I am not concerned exactly when is this delivered. I am just saying, "giveMeData. Deliver it to me whenever you can, whenever it's naïve for you. I just want to know what the data is at some point."
[52:18] For instance, that's the case for a click, you're asking for a click but you're not demanding it right now. You're just saying, "Once the click comes, I want it here." Even though we don't use forEach, with that idea in mind, it's the same case actually. It could be that the array.forEach takes many seconds to actually deliver data. It could be.
[52:48] There's nothing in this API that says that it will definitely be delivered as soon as possible. The nice thing about observables is that once you wrap them in the same abstraction, all of them are flexible to when they are going to arrays, sort of the data. I mentioned previously that of course map and filter exist for arrays but we could have map and filter for events like click. Let's do that since it's interesting use case.
[53:20] I am going to create, this one here is called observable1. I am going to call it numberObservable so I don't need to delete it. I am going to make another observable for click events. I am going to delete these console logs for now. I know that I can just wrap all of these stuff inside an observable just like it's inside here over subscribe. I am going to create a clickObservable equals createObservable.
[53:56] Observer is a bundle of callbacks. I can put this code inside that. Let's actually imagine that our element is actually the whole dome, just to make things easier. Document.addEventListener. Once, I guess, I get an event, I can just pass that to my observer. We don't need the error because you can't fail the click, unless you break or finger. You don't know exactly when is the user done with clicking.
[54:35] They could be thinking, and then they're going to click. We're not going to use the other callbacks. Instead of X here, it's supposed to event. That's clickObservable. That's how easy it was to make it. I can call the map and filter on it just like I would with arrays. I had here, this was the numberObservable. Instead, I am going to do the clickObservable.
[55:05] I am going to pipe it through some kind of map operations as well. The event here is a dome event. I think it has a clientX. That's the X coordinate. We could also have, let's say, the Y coordinate, clientY, so that we have the X and Y coordinates of the click. We could also pipe with a filter. Let's say I only want clicks that are in the left top corner. X needs to be smaller than, let's say, 200. Y needs to be smaller than 300.
[55:48] On the upper left corner of the screen, those are the only clicks that I want. I want their X and Y coordinates. We can say subscribe with an observer, same kind of observer that we had there for console log, just simple observer. We need to run this in the browser. I guess I need to copy-paste this into the browser. Let's put it here, for instance. I think about:blank is probably enough.
[56:18] It popped. It's over there. Sorry. Dock to bottom. Here we go. We have the console. I am going to paste all of my code there and run it. There it is. My observer is observing the clickObservable. Hopefully, if I click in the corner here, it says array 29, 21. It says clicks for each of these in the top left corner. If I click over there, nothing happens. That's pretty nice.
[56:57] It means that you can apply all of that, what you learned with map and filter, you could apply it, also, for clicks or streams. It's the same way of working. That's the nice thing. Once you have data, any type, data can be anything from strings to events to numbers, requests and responses. You can use the same way of working with an RxJS because you have all these operations.
[57:30] Now, of course, a lot of the magic of RxJS is just learning these operators. There's a couple more details. Maybe John has been noticing that I didn't talk about subscriptions. What do you guys think? Do you still want to hear about subscriptions, or do you have any other questions? I'm happy to talk about that.
Joel: [57:53] They would really appreciate it if you cut and pasted this into a gist real quick and then dropped that link into the chat so everybody can play with this code.
André: [58:00] Ah, like a GitHub gist.
Joel: [58:02] Yeah, just someplace where we can grab it, it seemed like a logical place.
André: [58:06] Gist.github, I'll just make it right here. Build your own RxJS, example. There we go. I hope you can find, just go to gist.github.com/staltz and the topmost is probably going to be the one that you think it is. Anything else?
Joel: [58:38] I think that was great, actually. That's one of the best overviews and ways of looking at Rx that I've ever seen. I've done this quite a bit, and we have, like Jafar Husain's course is great that we have on Egghead that describes this overall way of thinking, too. That was really super succinct and I enjoyed it quite a bit. Thank you.
John: [58:58] Thank you, and I do think, with the subscriptions, I see a lot of people start by teaching subscriptions, and hat can just add confusion where it's not, like that's a detail you don't really need to implement yourself. Just leave it to the library.
André: [59:14] Yeah. Another good link that I recommend is called toy-rx. If you go to github.com/staltz, and toy-rx, this repo takes what I just showed to you one step further and it adds subscriptions. I can show you the map, which is probably familiar. It looks very much like what I just showed you. Where we have, like, an object here with next, error, and complete, but there's also subscriptions going on.
[59:47] This is going to be super easy if you have followed this video, then this repository's going to look very familiar and easy to learn. I also demo more operators, just in case you want to know what's actually going on. This is like the best next step after this.
John: [60:07] Sweet, that's great.
Joel: [60:08] Yeah. Awesome. Thanks, André, I really appreciate you spending some time with us today, going over this stuff.