11 min read

I heard you like Promises! You should like them; they are awesome. I assume you are here because you have heard of something even more awesome and want to know more. In this small two-part post series, we’ll explore RxJS Observable with hints from your existing knowledge of JavaScript Promise. We’ll see how Observable can be used in places where you might be (incorrectly) using Promise now; we’ll understand what an Observable is, how similar/different from Promise it is; and we’ll see how Promise and Observable are actually best friends in disguise.

For our journey we’ll build a small app we will call “Code like Chuck Norris app.” It’s a ludicrously small/simple app, which will just show quotes about Chuck Norris and programming. Our real objective is to get inspired from Chuck Norris and eventually become a better coder.

For that we need to get the Chuck Norris inspirational quotes. We’ll do so by requesting quotes from the holy ICNDB. We’ll use superagent for making requests. It’s a really simple library that allows making Ajax requests from both Node.js and the browser. For starters, let’s turn that console in your browser to an inspirational one. Here’s our first test run:

import superagent from 'superagent';

let apiUrl;

apiUrl = `http://api.icndb.com/jokes/random?limitTo=[nerdy,explicit]`;

superagent
   .get(apiUrl)
   .end((err, res) => {
       let data,
           inspiration;

       data = JSON.parse(res.text);
       inspiration = data.value.joke;

       console.log(inspiration);
     });

Note that we are using ES6. You can check out the live examples on jsbin: http://jsbin.com/loyemo/edit. Fear not if you see a blank page opening that link. Just click on the Edit in JsBin button on the top right, and then turn on ES6/Babel and Console tabs in jsbin. Click on the run button to see the code running. Also make sure you have http://jsbin.com/* and not https. You won’t be able to make a request to ICNDB over https.

The code is fairly straight-forward. We ask superagent to make a GET request to our apiUrl, and when the request finishes, superagent invokes our callback. We don’t handle the error, parse the response JSON and console.log the inspiration.

Now that we know how to get inspired in the console, let’s turn this callback inspiration to promise based inspiration.

In this post, we’ll keep building our inspirational app in 2 codebases. Every part that we build, we’ll build it using both Promises and Observables. This will give you a good perspective of how things are done with both approaches. Plus, we are going to create both Promise and Observable from scratch, so to get an idea of how things work under the hood, and to see the similarities and differences between the two.

Promise

Promises are awesome. I liked them when I first heard of them. I mean, the ability to pass around the asynchronous computations like they are variables, what more can you ask from life?Here’s what our promise-based inspiration looks like (JsBin: http://jsbin.com/qolido/edit)

import superagent from 'superagent';

let apiUrl,
   promiscuousInspiration;

apiUrl = `http://api.icndb.com/jokes/random?limitTo=[nerdy,explicit]`;

promiscuousInspiration = new Promise((resolve, reject) => {
     superagent
         .get(apiUrl)
         .end((err, res) => {
             if (err) {
                 return reject(err);
             }
             let data,
                 inspiration;
             data = JSON.parse(res.text);
             inspiration = data.value.joke;
             return resolve(inspiration);
         });
});
promiscuousInspiration
     .then((inspiration) => {
         console.log('Inspiration: ', inspiration);
     }, (err) => {
         console.error('Error while getting inspired: ', err);
     });

For all the power they bring, Promises are simple business. Here we’re using the ES6 native Promise. For creating a new promise we pass the Promise constructor a function that receives two functions: resolve and reject. Inside this function we do our asynchronous work. We execute reject when an error happens, else we execute resolve when the asynchronous job gets its result.

And then we can carry this Promise around like a variable, and work with it elsewhere in the program. To use a promise, we use its then method, which takes two callbacks for success and error. Promises can do plenty more than this, but you already know that, right?

It’s about time to address the elephant in the room. Let’s create an Observable for the same task and see how it looks.

Observable

Observables are similar to Promises in that they both represent asynchronous jobs as first class things in Javascript. The similarity doesn’t end here. You can in fact think of an Observable as a Promise that can resolve more than once. Or more aptly, we have the plural of Promise. We’ll come to that later, for now, let’s port our inspirational console to use Observable.

JsBin: http://jsbin.com/foweyo/1/edit
import superagent from 'superagent';
import {Observable} from 'rx';
let apiUrl,
   reactiveInspiration;

apiUrl = `http://api.icndb.com/jokes/random?limitTo=[nerdy,explicit]`;

reactiveInspiration = Observable.create((observer) => {
   superagent
       .get(apiUrl)
       .end((err, res) => {
           if (err) {
               return observer.onError(err);
           }
           let data,
               inspiration;
           data = JSON.parse(res.text);
           inspiration = data.value.joke;
            observer.onNext(inspiration);
           observer.onCompleted();
       });
   return () => {
       console.log('Release the kraken!');
   };
});

reactiveInspiration.subscribe(
   (inspiration) => {
       console.log('Inspiration: ', inspiration);
   },
   (error) => {
       console.error('Error while getting inspired', error);
   },
   () => {
       console.log('Done getting inspired!');
   }
);

Observable.create is a helper for creating Observable for a specified subscribe method implementation (we’ll see what that means in a minute). It looks very similar to creating a Promise but there are some differences. Let’s walk over them from bottom up.

  • reactiveInspiration.subscribe

    To use the value of a Promise, we use its ‘then‘ method. For using an Observable, the equivalent is its subscribe method. First question that pops to mind is why the weird name? I mean aPromise.then makes perfect sense “when this promise resolves, then do this”. As I hinted above, an Observable is not just a single value. It represents a series of values. You can think of it as a data stream to which we subscribe.

  • Three callbacks to subscribe

The second thing to note is three callbacks being passed to subscribe instead of just two. Again, two callbacks for success and error makes perfect sense, but what is that third one for? Well, Observable is not a single computation that’ll succeed or fail, it is a series. Arrays have an implicit null value that marks the termination of Array, a value that tells the loop (or whatever uses the array) that, “We are done.” Observables are the same as arrays in that they’re a collection, or an asynchronous collection of asynchronous values, but still a collection (actually calling it an Iterable would make more sense but they’re similar enough to be assumed same for this case). A collection needs to tell its consumer when it finishes. That is what third callback does. It’s called onCompleted callback, and it gets invoked when the Observable is completed and will not receive any more values. In our simple example it gets completed right after receiving one value.

The other two callbacks are called onNext and onError in that order. Together this trio makes a subscriber of the Observable. We can actually pass an object to the subscribe method with these three methods as keys (JsBin: http://jsbin.com/foweyo/2/edit):

reactiveInspiration.subscribe({
   onNext: (inspiration) => {
       console.log('Inspiration: ', inspiration);
   },
   onError: (error) => {
       console.error('Error while getting inspired', error);
   },
   onCompleted: () => {
       console.log('Done getting inspired!');
   },
});

Implicitly, RxJs will create a Subscriber with the methods we provide and use default for those we don’t provide, but let’s not get technical about it.

Now that you know what a subscriber is for an Observable, let’s move up the code and see how to create an Observable from scratch with Observable.create. Observable.create, which creates an Observable from specified subscriber implementation. It’s actually very simple in practice.

Observable.create’s callback is given an object as argument, which provides our three favorite methods: onNext, onError, and onCompleted.

Just like we do with Promise, we do our asynchronous work in the callback we pass to Observable.create. We call the appropriate callback when the asynchronous job get its value or some error happens, or in the case of Observable, when we are done sending values from the Observable (i.e when the asynchronous job is finished).

In our app, the first thing we do is check if the request as resulted in an error. If it does, we call the onError method of the observer we receive in the callback. When we get the value, we call observer.onNext. In our case we are only interested in one value, so we mark our Observable finished right after that by calling onCompleted of the observer.

  • Release the Kraken?

This all was quite similar to the Promise, but what is that function we are returning at the end? It’s a function, which releases all the Krakens you use in your asynchronous operation. All of the resources that you need to get rid of when your Observable need to quit. In our case we don’t have anything we want to get rid of, but there are times when we do. For example, if you haven’t already guessed, we can represent almost any asynchronous operation in JavaScript as an Observable. That includes operations like events (yes including DOM events). The dark art of DOM-events can end up being pretty expensive if we forget to remove the event-listeners. So that’s what we can release in this function. Just imagine how powerful this can end up being. We defined what resources to release right when we started using them! If you can’t yet see the power it delivers, then just trust me on this one (as well). Being able to get rid of potentially expensive resources declaratively with a standard interface is very powerful. We’ll taste a sip of this power in next part of this post. This function can be called the dispose function of the Observable. And the Observable can be said to be implementing the Disposable interface if you’re into those things.

The finishing part of the asynchronous operation is the default in case of a Promise. When a promise gets a value or when it meets an error, it is finished. An Observable differs from a Promise only in case of getting a value. The Observable doesn’t finish when we invoke onNext. But for the other part (facing an error), they are the same as Promise. An Observable stops in two situations:

  • when it receives onCompleted
  • when it faces an error

If any error occurs in the Observable, it stops right there and will not emit any more values unless explicitly asked to do so. It also calls its dispose function when an Observable finishes. So it’s guaranteed that an Observable’s resources will get freed whenever it finishes, naturally or by error.

That’s all there is you need to know to get started with Observables. A Promise represents a single asynchronous value, like a regular variable. Observables on the other hand represents a series of asynchronous values, like an array.

Laziness

The second noticeable difference you might need to know before we move to next part of this tutorial is that Observables are lazy while promises are not. In our example, let’s write a comment in both cases whenever a value is arrived from the API.

JsBin: http://jsbin.com/zavidi/1/edit
import superagent from 'superagent';

let apiUrl,
   promiscuousInspiration;

apiUrl = `http://api.icndb.com/jokes/random?limitTo=[nerdy,explicit]`;

promiscuousInspiration = new Promise((resolve, reject) => {
   superagent
       .get(apiUrl)
       .end((err, res) => {
           if (err) {
               return reject(err);
            }

           let data,
               inspiration;

           data = JSON.parse(res.text);
           inspiration = data.value.joke;

           console.log('Inspiration has arrived!');
           return resolve(inspiration);
       });
});

// promiscuousInspiration
//     .then((inspiration) => {
//         console.log('Inspiration: ', inspiration);
//     }, (err) => {
//         console.error('Error while getting inspired: ', err);
//     });

And let’s do equivalent for Observable part (JsBin: http://jsbin.com/zavidi/3/edit):

//in observable.js
import superagent from 'superagent';
import {Observable} from 'rx';

let apiUrl,
   reactiveInspiration;

apiUrl = `http://api.icndb.com/jokes/random?limitTo=[nerdy,explicit]`;

reactiveInspiration = Observable.create((observer) => {
   superagent
       .get(apiUrl)
       .end((err, res) => {
           if (err) {
               return observer.onError(err);
           }

           let data,
               inspiration;

           data = JSON.parse(res.text);
           inspiration = data.value.joke;

           console.log('Inspiration has arrived!');
           observer.onNext(inspiration);

           observer.onCompleted();
       });

   return () => {
       console.log('Release the Kraken!');
   };
});

// reactiveInspiration.subscribe({
//     onNext: (inspiration) => {
//         console.log('Inspiration: ', inspiration);
//     },
//     onError: (error) => {
//         console.error('Error while getting inspired', error);
//     },
//     onCompleted: () => {
//         console.log('Done getting inspired!');
//     },
// });

Notice that we have commented out the part of the code that actually uses our Promise/Observable. Now if you run the app for both files, you’ll notice that the promise code makes the request and invokes the code (you’ll see a log in console) even though we don’t really use the Promise. The Observable, however, acts as if it doesn’t even exist. This is because Observables are lazy. An Observable won’t execute code until it has at least one subscriber. We can deduce here that a let p = new Promise(…) actually carries the “value” of the promise which was obtained ages ago when the promise was created. On the other hand, an Observable carries the complete computation with it, which is executed on-demand. This laziness (like most other features of Observable) makes them very powerful. This also means that we can reproduce (re-execute) an Observable from itself (for example when you wanna retry on error), while to do the same for a Promise we need to have access to the code that generates the promise. Now that we understand what Observables are, we shall practice their real powers in next part of this tutorial.

About the author

Charanjit Singh is a freelance developer based out of Punjab, India. He can be found on GitHub @channikhabra.


Subscribe to the weekly Packt Hub newsletter. We'll send you the results of our AI Now Survey, featuring data and insights from across the tech landscape.

* indicates required

LEAVE A REPLY

Please enter your comment!
Please enter your name here