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:
- “Next” notification: The actual data (such as String, Number, an Object etc.) being delivered to the observer.
- “Error” notification: sends a JavaScript Error or exception.
- “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 theObservable
is lazy. ThePromise
is eager, since the executor function (passed as the constructor argument) gets invoked at the moment of its definition. TheObservable
is lazy, since the function does not get executed until you subscribe to theObservable
.Promise
is always asynchronous whereas theObservable
can deliver values synchronously and asynchronously.- The
Promise
only delivers a single value, whereas theObservable
may emit multiple values. - Observables’
subscribe()
is responsible for handling errors.Promises
push errors to the child promises.