Learnitweb

Observables in RxJS

1. Introduction

Observables are lazy Push collections of multiple values. That is an Observable can push multiple values to its subscriber. The term lazy in the definition means that the Observable will not push values to the subscriber until subscribe is called. This is analogous to a function call, which does not execute until called.
An Observable can synchronously or asynchronously return zero to infinite values from the time it’s invoked.

Let us now briefly discuss what is meant by the term Push in the definition.

2. Push vs Pull

Push and Pull are two mechanisms using which a Producer and a Consumer can communicate with each other.

In Pull systems, the Consumer determines when it receives data from the Producer. The Producer is unaware of when the data will be delivered to the Consumer. A JavaScript function is an example of a Pull system. The client code which calls the function is the Consumer and the function itself acts as a Producer of data which returns a single value. If you return an array from a function even then it is a single value (a collection of values). Generator functions introduced in ES2015 is another example of Pull Systems. The generator function (function*) is the producer of data. The Consumer calls next() method to pull values from the Producer.

In Push systems, Producer determines when to send the data and the Consumer is unaware of when it will receive the data from the data Producer. Promises in JavaScript is an example of Push system. Once a Promise is resolved, it pushes a resolved value to the registered callback.

3. Create an Observable

An Observable is created using new Observable or a creation operator. Most commonly, observables are created using creation functions, like of, from, interval, etc.

import { Observable } from "rxjs";

const observable = new Observable(function subscribe(subscriber) {
  const id = setTimeout(() => {
    subscriber.next("hello world");
  }, 1000);
});

observable.subscribe((value) => console.log(value));

This will print the ‘hello world’ on the console after one second.

4. Subscribing to Observables

Subscribing to an Observable is like calling a function. The only difference is in case of Observable, we provide a callback to which the values are pushed.

observable.subscribe((value) => console.log(value));

The Observable constructor takes one argument which is the subscribe function. When an Observer calls observable.subscribe, the function subscribe(subscriber) passed as an argument to the Observable constructor is called separately for that call. So the subscribe calls are not shared among multiple observers of the same Observable.

You need to subscribe to the Observable as an Observable will not push values of its own. An observer needs to subscribe to the Observable.

Subscribing to an Observable is analogous to calling a Function.

5. Executing Observables

The subscribe function passed to the constructor as an argument, is executed for each subscriber that subscribes to the Observable. An Observable can produce values synchronously as well as asynchronously. An Observable can produce a single value as well as multiple values.

An Observable execution can deliver three types of values to the observer:

  1. “Next” notification: The actual data (such as String, Number, an Object etc.) being delivered to the observer.
  2. “Error” notification: sends a JavaScript Error or exception.
  3. “Complete” notification: the complete notification. This does not send a value.

“Error” and “Complete” notification are mutually exclusive, that is only one of them is delivered by the Observable. If during an Observable execution, either Error or Complete is delivered then afterwards nothing else is delivered afterwards.

5.1 next() example

import { Observable } from "rxjs";

const observable = new Observable(function subscribe(subscriber) {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
});

observable.subscribe((value) => console.log(value));

Output

1
2
3

5.2 complete() example

import { Observable } from "rxjs";

const observable = new Observable(function subscribe(subscriber) {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  subscriber.complete();
  subscriber.next(4); // it will not be delivered to the observer
});

observable.subscribe((value) => console.log(value));

Output

1
2
3

6. Disposing Observables

Once the observer is done, it is wise to unsubscribe the Observable to stop wastage of computation power and memory. The observable.subscribe method returns a subscription. You can use subscription.unsubscribe() to cancel the ongoing execution.

Each Observable must define how to dispose the resources.

import { Observable } from "rxjs";

const observable = new Observable(function subscribe(subscriber) {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  
  
});

let subscription = observable.subscribe((value) => console.log(value));
subscription.unsubscribe(); 

In this simple example, Observable delivers finite values. After the observer is done, we call unsubscribe method. When an Observable delivers infinite values, unsubscribe method is very helpful as it will help to cancel the ongoing execution. The unsubscribe method is also helpful for cleanup of resources if there is any error.

7. Observables are not event handlers

Observables look like event handlers but they are not. However, an Observable can act as an event handler. In case of event handler APIs, listeners are registered whereas in case of Observable, observer is not registered as a listener. An Observable does not maintain a list of observers.

8. Observables can deliver values synchronously

It is the perception that Observables are asynchronous. But it is not true, Observables can deliver values synchronously.

import { Observable } from "rxjs";

const observable = new Observable(function subscribe(subscriber) {
  subscriber.next("value from observable");
});

console.log("before calling subscribe");
observable.subscribe((value) => console.log(value));
console.log("after calling subscribe");

Output

before calling subscribe
value from observable
after calling subscribe

9. Observables can deliver values asynchronously

Observables can also deliver value asynchronously.

import { Observable } from "rxjs";

const observable = new Observable(function subscribe(subscriber) {
  setTimeout(() => {
    subscriber.next("value from observable");
  }, 1000);
});

console.log("before calling subscribe");
observable.subscribe((value) => console.log(value));
console.log("after calling subscribe");

Output

before calling subscribe
after calling subscribe
value from observable

10. Observables vs Promises

  • Promise is eager, whereas the Observable is lazy. The Promise is eager, since the executor function (passed as the constructor argument) gets invoked at the moment of its definition. The Observable is lazy, since the function does not get executed until you subscribe to the Observable.
  • Promise is always asynchronous whereas the Observable can deliver values synchronously and asynchronously.
  • The Promise only delivers a single value, whereas the Observable may emit multiple values.
  • Observables’ subscribe() is responsible for handling errors. Promises push errors to the child promises.