Let's talk Observables.

If you have spent any length of time with either front-end development or the world of NodeJS you will be no stranger to the difficulties of handling asynchronous code, hopefully, you also have a fair few tools to help you out.

First off we have our 'trusty' callback function but this soon becomes a complete nightmare once we start having to nest callbacks, the code spirals into a complete mess where you will be lucky to find any level of readability or resilience to bugs.

This brings us on to Promises, they are very useful, still useful in fact but again experience has told us that chaining Promises quickly becomes a nightmare, code starts to become overly complex and without a lot of due care and diligence the code is straight up unreadable.

There are plenty of examples out there of the above, you have probably thought of a few places in your code base or a past code base where the above assertions are all too real, so what is the solution?

Observables right? Well, they are unlikely to solve all your problems overnight partly to there is no silver bullet to any given problem but also because the concept of an Observable is unlike any other we have seen that tackles asynchronous behavior.

What do I mean by this? Surely I am not insinuating that the solution I am suggesting you become familiar with is inherently complicated!? That flies in the face of common sense after all - no don't worry Observables are not complicated...only different.

With a Promise, we make a call to say a remote API and then wait for the resolution and perform some code based on whether the Promise resolved or errored and of course, probably based on the response received from whatever we have called out for!

With an Observable, you handle this in a different way, first you call out to your remote API, then at some point the Observable will emit a value but in general, only if something is subscribed to that Observable, wait what are all these words? emit? subscriptions? What is this?

Let's tackle the word 'emit' first, an Observable is not representative of a single value, you are not unwrapping a value from an observable, instead think of an observable as a stream or, if you prefer; an unknown number of values over time.

This might seem confusing, surely when you call an API you are only expecting one response, well yes but Observables are much more than a way to handle calling remote APIs, indeed they can do this and do it well, in fact, the Angular HttpClient returns an Observable by default as opposed to a Promise.

At this point let's get a piece of code out into the open, full disclosure, this is a shameless copy from the RxJs website, the example below is what started to get me to click as to what an Observable is.

import { fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';

const source = fromEvent(document, 'click');
const example = source.pipe(map(event => `Event time: ${event.timeStamp}`))
const subscribe = example.subscribe(val => console.log(val));

JsFiddle: https://jsfiddle.net/btroncone/vbLz1pdx/ (note the code in this fiddle does not use the pipe, this is a change in version 6 of RxJs)

Let's talk line by line, first off let's talk about the imports, the first imports the fromEvent, which takes an event and wraps it in an Observable, there are many such functions that turn many things including promises, arrays, strings and so much more into an Observable, I highly recommend checking out the RxJs website to get a feel for all the functionality that is available.

The second import imports the map operator which will let us transform an event from one form to another which we will see further on in the code.

We see fromEvent being used to create an Observable from the click event on the document, now anything that subscribes to the source variable will receive values as a result of the Observable emitting a value for each click.

Next, we see the map operator being used, RxJs 6 makes use of the pipe function, when we want to perform operations we import the operator and pass that into the pipe of our Observable, in the example above the map operator is used to transform our emitted value into a human-readable message.

Finally, the example variable is subscribed to and then we see that for each value we print out the 'val' variable which from going through the pipe we created is a human-readable message.

We can also do this which is the same as the above code fragment:

import { fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';

fromEvent(document, 'click').pipe(
    map(event => `Event time: ${event.timeStamp}`)
)
.subscribe(val => console.log(val));

I do have a preference though, for the following form, I like to handle side effects using the tap operator:

import { fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';

fromEvent(document, 'click').pipe(
    map(event => `Event time: ${event.timeStamp}`),
    tap(val => console.log(val))
)
.subscribe();



Do whatever works for you, I can see no reason why either of these three forms is better or worse than the others.

What I like about this code is that it is declarative, expressive and easy to read. What's more is we can d a lot more with our click and still not impact the expressiveness and readability of our code.

If you are a front-end developer you should really look to get to grips with Observables, if you are currently using Angular or React then you would be mad not to have an iron grip on them!

This has hardly scratched the surface of what can be done with Observables, we still need to address logging, error handling and other things that you may not even know you want to know until you read about it.

If it is not already apparent I am a fan so I will no doubt be writing more on Observables and RxJs in general but I sincerely hope this has piqued your curiosity and encouraged people to refrain from calling 'toPromise' on their observables.


Comments

Popular posts from this blog

Angular material table with angular 7 - Defining columns using ngFor

Creating contextual drop downs using RxJs, Angular 7 and Angular Material

From nothing to an Angular 7 app running in 5 minutes with AWS