Skip to main content
Home  ›  Blog

JavaScript: Observables in Reactive Programming - Stop Making Promises

JavaScript Promises are great for simple "I need to wait a bit" callbacks. But they change your architecture in a way which doesn't scale for larger applications. And JS is the future of GUIs, so it's time stop making promises. 

Why Promises Don't Scale

If you've believed promises are a great thing, you read the post Promises are GOTO evil incarnate and google callback hell

Promises work well if all you only want to "wait" for something to happen, and you're application is very simple. It's great if your JS loads 1 initial list from a server and then renders it to a table. Beyond that, things get messy. Here's a diagram, of how a view depends on various things for it to show the right data:

The diagram is actually very clear and simple - it's obvious which things rely on what. But in your Promises-based code the relationships get very muddy. You will have various bits of code across your application trying to ensure that this happens. Common problems are: 

  1. if your application requests more data based on user interactions, things get more complex with multiple requests - in this case, the user can change categories and search-terms
  2. if some responses are delayed unevenly (like with http), sometimes an older request arrives after a newer request,  resulting in UIs showing the result of the search "java" even though the user wrote "javascript" in the search, but the "java" results came back last
  3. if a request takes more than a second to complete, users will start hitting buttons causing multiple requests and dialog-changes, which end up in strange, conflicting situations
  4. if your application has multiple sources of data, which don't arrive together, you'll start writing messy code - and it gets worse if certain sources update while others don't and the result must be merged somehow

So if there would be a stream-programming paradigm, this would be very simple. But doing this in any JS + Promises kind of setup quickly leads to many hours of fixing, plumbing and hacking about. And the solution never really feels reliable. Frameworks like React, Vue and AngularJS have helped a bit, but much of the remaining problems are strongly tied to this architecture.

Reactive Programming and Observables to the Rescue

I learned about observables when I started with Angular 4, but I must admit that the more I learn, the more I realize that Redux (a popular React data thingy) had this part figured out a long time ago. And when I started learning observables, I first though they are just Promises++. They are not.

To help you understand the profound impact, I'll cover these parts

  1. Basics: What are Observables technically, and how are they different from promises
  2. The Magic: Observable Chaining, Operators and pure functions
  3. How Observables lead to Reactive Programming
  4. ReactiveX (RxJS) and what this means
  5. One-Way Data Flow - the Flux/Redux model, which simply kills MVC
  6. The joy of untangling components which you thought were independent 

#1: Basics What are Observables Technically?

Again the breakdown, we'll look at

  • Events - like a marble with a message inside
  • Event-Streams - like a pipe with marbles inside
  • Observable - a source of events (releasing marbles)
  • Observer - a marble-catcher
  • Subscriptions - the pipes connecting Observables

An Event is like a Marble with Message inside

To start, I must explain what an event is, because this is what we'll end up observing. An Event is like a marble with a message inside. Here are some examples of such event-marbles:

  • Event 0: The time of application start is known, the message is "13:53:00"
  • Event 1: Data arrives from a server, the message is "{ some json }"
  • Event 2: User typed "2" in the search-box, the message is "2"
  • Event 3: User typed "2s" in the search-box,  the message is "2s"
  • Event 4: User clicks on "reload", the message is empty
  • Event 5: the mouse moved, the message is "x=405,y=503"
  • Event 6: ten second have passed, the message is "13:53:10"
  • Event 7: Event 0 and Event 6 have both fired, the message is "10" (time difference in seconds)

An Event Stream is a Pipe with Event-Message Marbles

As events are fired - because new data arrives, because a user clicks, these are passed along as a stream. This stream is not like a file-stream with a continuous set of data being buffered, but more like part of a marble run, which has a marble roll through it from time to time, with the message inside the marble.

An Observable is a Spout Releasing Marble-Messages

  • An Observable is a source of such event-marbles. It keeps on sending event-messages
  • An Observable may provide changing information (like the time) or never-changing information, like the number Pi
  • These messages...
    • ...can come from "inside" the Observable (like seconds on a clock)
    • ...may be passed into it at start (like a almost-constant value)
    • ...may come from "outside" like JSON-data arriving
  • A promise fires only once (success or error), an Observable can fire as much as it wants to
  • A promise only delivers data at the moment it resolves, an Observable can still provide the old data if asked after the event.

Example of an Observable Event Emitter

const source = new Rx.Observable(observer => {
  observer.next("first event");
  observer.next("second event");

  setTimeout(() => {
    observer.next("a second has passed");
  }, 1500);
});

 

An Observer is Marble-Catching Funnel

  • An Observer listens to these "Marble" event-messages, catching them like a funnel
  • Whenever an Observer receives a message, it can use it or ignore it
  • If the Observer uses this Marble-message, it can do with it whatever it wants to
  • Whenever a new "Marble" arrives, the Observers "onNext(…)" function is called with this new Marble
  • Observers also have onError and onCompleted for special use cases

Sample Showing An Observable with An Observer

/*
  timer takes a second argument, how often to emit subsequent values
  in this case we will emit first value after 1 second and subsequent
  values every 2 seconds after
*/
const source = Rx.Observable.timer(1000, 2000);

//output: 0,1,2,3,4,5......
const subscribe = source.subscribe(val => console.log(val));

 

Subscriptions are the Connection Pipes

  • Observers are hooked up to Observables by opening a Subscription
  • Subscriptions can be cancelled whenever needed
  • Observables can have 0, 1 or many Observers subscribing to them (with the right wiring)
  • Observers can have 0, 1 or many Observables sending it messages (with the right wiring)

Another Example with Multiple Subscriptions

/*
  timer takes a second argument, how often to emit subsequent values
  in this case we will emit first value after 1 second and subsequent
  values every 2 seconds after
*/
const source = Rx.Observable.timer(1000, 2000);

//output: 0,1,2,3,4,5......
const subscribe1 = source.subscribe(val => console.log(val));

// another subscription, which just takes the first hit and does something
source.skip(2).take(1).subscribe(
  // in here we'll add another subscriber - after the first events passed
  //output: 2,3,4,5......
  const sub2 =  source.subscribe(val => console.log(val));
);

 

Events (Marbles) Only Flow Downhill

Messages only go from Observables to Observers...and never the other way back

Operators, Subjects, Pure Functions and more

To keep this post on a reasonable length, I'll stop here - but we actually just got started. I'll continue with the magic part in a next post.

Love from Switzerland,
iJungleboy


Daniel Mettler grew up in the jungles of Indonesia and is founder and CEO of 2sic internet solutions in Switzerland and Liechtenstein, an 20-head web specialist with over 800 DNN projects since 1999. He is also chief architect of 2sxc (see github), an open source module for creating attractive content and DNN Apps.

Read more posts by Daniel Mettler