To understand observable you must become one
Observables. Seems quite hard to all of us, imperatively thinking human beasts. Looks like asynchronous tasks seems clear to us as long as they are in callback chain (the bigger, the better). But when in one place you should define observable, in other place observer and control application flow via subscription most of us seems to be lost. So, as Sun Tzu says, “To understand your enemy you must become one” (reference is here. And by the way, he was a quite smart guy, smart guys are writing books). Let’s become an observable. Consider below code, where we create observable:
const emitter = Rx.Observable.create((observer) => {
document.body.onkeyup = function(e){
if(e.keyCode !== 27 && e.keyCode !== 69) {
var valueToEmitt = String.fromCharCode(e.keyCode)
observer.next("I'm done")
}
else if (e.keyCode === 27) {
observer.completed();
}
else if (e.keyCode === 69){
observer.error("E key pressed...")
}
}
})
Congratulations. You manually make yourself an observable that is able to emit some data (mimic keystrokes), complete itself (on Esc key press) or even invoke error (after E key), all with usage of keyboard. Not much of superpowers, but still nice. Anatomy of observable is surprisingly easy. Static factory method create requires single object: observer. This object shall be implementation of observer interface (reference is here):
myObservable = {
next: (x) => {},
error: (error) => {},
completed: () => {},
}
So just object with three functions. "next" function will be invoked when observable emit new value. "error" function will be invoked when...well, when observable will emit error. And "completed" when observable will finalize itself - when there will be no more values to emit.
Well OK, but when the body of observable will be actually invoked? If you would launch above snippet, nothing will happen, no values are printed. You are rather lonely observable now, because no one observes you and your emitted values. No one subscribes to your activity, so all things you do in observer body doesn't matter. Even worse, they will never actually execute, . Let's change that by creating your loyal fan, subscriber:
var subscription = emitter.subscribe((value) => {
console.log("Emitted value: " + value)
},(error) => {
console.log(error)
},() => {
console.log("Observable finished.")
}
)
After launching this code, you will see, that first function of subscriber is invoked each time you press some keyboard key (except 'E' and 'Esc'). Someone is finally listening to your values. Finally.
Interactive scenario
Now let's build up a scenario: you are observable (still) but someone may be occasionally interested in what you are emitting. Like you occasionally interested in what's on TV, you can turn it ON (subscribe) and OFF (unsubscribe). To mimic that, let's add to HTML template two simple buttons:
<button id="subscribeButton">Subscribe</button>
<button id="unsubscribeButton">Unsubscribe</button>
And a bit of code to serve them. To subscribe button I just copy-paste code responsible for subscription to observable:
var subscribeButton = document.getElementById('subscribeButton');
subscribeButton.addEventListener('click', ()=>{
console.log("Subscribing to observable!");
subscription = emitter.subscribe((value) => {
console.log("Emitted value: " + value)
},(error) => {
console.log(error)
},() => {
console.log("Observer finished.")
}
)
})
To unsubscribe button I added just a single unsubscribe call:
var subscribeButton = document.getElementById('unsubscribeButton');
subscribeButton.addEventListener('click', ()=>{
console.log("Unsubscribing from observable!");
subscription.unsubscribe();
})
And important note: declaration of subscription must be now elevated to document scope:
var subscription;
Now when you launch console, no values will be printed, until you click on subscribe button. Only after that observer is attached (registered) to observable, observable itself is executed and onkeyup function is really invoked.
CodePen is here. Feel free to click around. And keep your developer console open.
Why this simple scenario matters?
Imagine asking backend for some values. Let's suppose that due to some limitations server is able only to query single value per second. You requested three values. Do you wish to freeze your UI for three seconds? Nobody (at least nobody reasonable) wants that. How to solve? Use observable.
Subscribe to observable provided by HTTP framework, register "next" function and let it go. You will receive asynchronously three calls to next, each time server will share it. Do with them whatever you need at point of emission. Keystroke printing is equivalent in our example.
After two seconds you get bored. You don't want last, third value. For any reason you don't wont to execute next on it. Just unsubscribe at any given time to stop taking care about observable emission. Unsubscribe button is equivalent in our example.
Server crashed after first emission. You registered error function in your subscriber. You will be notified and safe fall back strategy can be implemented. Pressing 'E' key and printing error message is our equivalent.
So what about promises and stuff...?
Native solution to asynchronous tasks is promise. They are fine, at very basic level they are even similar to observables (but if someone claims they are identical, he lies). And conceptually they are way easier than observables. Things are getting drastically different when you have to deal with series (or, as Rx lovers like to call it, streams) of data. Promise is able to handle single data emission, and dies silently. Your subscription to observable remain active as long as you wish (until you call "unsubscribe") or observable owner will terminate it via completed. Observable support chaining transform operators, which are one of the most awesome things you will ever meet in your programming life. But this is separate topic, which at least for me was (and still is) challenging to master. It's worth to know both of those solutions. And as I mentioned, on basic level both:
observable.subscribe(val =>{
/* Do stuff */
}).flatMap((val)=>{
/* Do next stuff */
}).flatMap((val)=>{
/* Do next stuff */
})
and:
somePromise.then(val =>{
/* Do stuff */
}).then(val =>{
/* Do stuff */
}).then(val =>{
/* Do stuff */
})
will work very similar.
Just a side note:
((a, b)=>{
a.find((n,i,v)=>{
return a.v === b ? false : true;
}).map((k)=>{
k = a*2
})
})
If you think that lambdas are ugly, than you probably have some more conservative programming background as I did. And at some level you are probably right. When I finally came out from deep and dark world of embedded C89 world this looks strange and unreadable to me as well. But lambdas are power, trust me. And closures. And generators. And all that stuff that seems so wired after moving one step forward from low level programming.
Even more side note:
Thanks to my wife to encourage me to share new things I learn.
Thanks to my little daughter to give me 1 hour of free time to write this post.
Love ya.