Quote of the Day

more Quotes

Categories

Get notified of new posts

Buy me coffee

Differences between a Promise and an Observable

In this post, I outline a few fundamental concepts I have learned about an Observable and how it is different than a Promise.

Before discussing the differences between an Observable and a Promise, let’s talk about what they have in common. Both Observables and Promises are frameworks for producing and consuming data. They follow the push protocol which means the producer determines exactly when to send the data to the consumer. In comparison, in a pull protocol, the producer only produces data when the consumer asks for it.

In a Promise, the consumer is the resolve callback that you pass to the constructor when constructing the promise, and the producer is the promise itself that invokes the callback asynchronously to pass the data. As an example, in the below snippet, once the promise (producer) finishes after one second, it invokes the callback (consumer) and passes the string “Work is done on Promise” (data).

 const executor = (resolve, reject) => {
  // perform some work
  console.log("Work started on Promise");
  setTimeout(() => resolve("Work is done on Promise."), 1000);
};

const aPromise = new Promise(executor);
aPromise.then(value => console.log(value));

In an Observable, the producer is the observable and its observers are the consumers.

// Producer
const anObservable = new Observable(subscriber => {
  console.log("Work started on Observable");
  setTimeout(() => {
    subscriber.next("Work is done on Observable");
    subscriber.complete();
  }, 1000);
});
// Consumer
anObservable.subscribe(
  value => console.log(value)
);

Although you can use either an Observable or a Promise to produce and receive data, an Observable and a Promise are considerably different in terms of design and functionalities, as we shall discuss in the subsequent sections.

Multi vs Single Use

The first fundamental difference between an Observable and a Promise is that an Observable can emit multiple values whereas a Promise can emit only a single value.

In the code snippet below, the observer emits two values and then completes.

const anObservable = new Observable(subscriber => {
  console.log("Observable started");
  subscriber.next(1);
  subscriber.next(2);
  subscriber.complete();
});

anObservable.subscribe(
  value => console.log(value),
  err => console.log(err),
  () => console.log("Observable completed")
);

Below is the output in the console:

Observable started
1
2
Observable completed

Unlike an Observable, a Promise can only emit a single value, as shown in the example snippet below.

const aPromise = new Promise((resolve, reject) => {
  console.log("Promise started");
  resolve(1);
  resolve(2);
});
aPromise.then(value => console.log(value));

When the promise invokes the resolve callback the first time, the consumer receives the first value via the then() clause. However, the second invoke has no effect, as you can see only the first value shows up in the output.

Promise started
1

Eager vs Lazy

Another difference between an Observable and a Promise is that an Observable is lazy whereas a Promise is eager.

A Promise is eager because execution starts right away without waiting for the consumer. For example, in the below snippet, on instantiating the promise, console.log() would execute although no consumer acts on the promise (by virtue of invoking the then() clause).

const aPromise = new Promise((resolve, reject) => {
  // The line below gets executed right away
  console.log("Promise started");
  // The resolve() only gets call when the consumer wants the data (via the then() clause). 
  resolve(1);
});

Here is the output from the console: Promise started

On the other hand, an Observable is lazy because it does not start work until the first observer subscribes to it.

const anObservable = new Observable(subscriber => {
  // The line below does not get called until a subscriber subscribes to the observable. 
  console.log("Observable started");
  subscriber.next(1);
  subscriber.complete();
});

// anObservable.subscribe(value => console.log(value));

To see the output, uncomment the last line of code to subscribe to the observable.

Below is the output:

Observable started
1

Cancelable vs non-cancellable.

The next difference is that an Observable is cancelable whereas a Promise is not.

Because a Promise is eager and only for a single use, it does not support cancellation, at least not natively. For demonstration, consider the snippet below:

const aPromise = new Promise((resolve, reject) => {
  console.log("Promise started");
  var counter = 1;
  const intervalId = setInterval(() => {
    console.log(counter++);
    if (counter > 10) {
      clearInterval(intervalId);
      resolve("Promise finished");
    }
  }, 1000);
});

The above code simulates a long running operation that take 10 seconds to complete, and it outputs the value of the counter variable on every second. While execution is running, if the caller decides to cancel, the caller has no way to cancel. The promise just executes all the way until it finishes. You would need to build cancellation yourselves or use a third party library.

Following is the output:

Promise started
1
2
3
4
5

In contrast, an Observable supports cancellation natively. Let’s modify the example above to use Observables and see how we can cancel the operation before it finishes.

import { interval, Observable, Subscription } from "rxjs";

const anObservable = new Observable(subscriber => {
  console.log("Observable started");
  var counter = 1;
  const intervalId = setInterval(() => {
    if (subscriber.closed) {
      console.log("Received cancellation");
      clearInterval(intervalId);
      subscriber.complete();
    }
    subscriber.next(counter++);
    if (counter > 10) {
      subscriber.complete();
    }
  }, 1000);
});
// subscribe to receive values from the observable.
const subscription: Subscription = anObservable.subscribe(value =>
  console.log(value)
);
// cancel the susbcription after 2 seconds
setInterval(() => subscription.unsubscribe(), 2000);

In the above snippet, the consumer signals cancellation to the producer by invoking the unsubscribe() method on Subscription. In case you are unfamiliar with Subscription, it basically exposes methods to manage and cleanup resources associating with subscribing to the observables. For the producer, it can check for the cancellation request by inspecting the variable closed. Below is the output:

Observable started
1
2
Received cancellation

Multicast vs unicast

Yet another difference between a Promise and an Observable is that an Observable can multicast data to multiple observers whereas a Promise only runs once and return data to its one and only consumer.

For demonstration , consider the snippet below:

const aPromise = new Promise((resolve, reject) => {
  console.log("Promise started");
  setTimeout(() => resolve(1), 1000);
  setTimeout(() => resolve(2), 2000);
});
aPromise.then(value => console.log(value));
aPromise.then(value => console.log(value));

Calling the then() method the second time does not make the promise to execute again, as the promise has already finished. In fact, once the promise has finished, it caches the result and return the cached value for all subsequent invocation of the then() method, as shown in the below output.

Promise started
1
1

Let’s now look at an example of how an observable can be multicast which means they can broadcast data to multiple subscribers.

const anObservable = new Observable(subscriber => {
  console.log('Observable started.');
  setTimeout(() => subscriber.next(1), 1000);
  setTimeout(() => subscriber.next(2), 2000);
});
// each call to subscribe() results in a new Subscription
const subscription: Subscription = anObservable.subscribe(value => console.log(value));
const anotherSubscription: Subscription = anObservable.subscribe(value => console.log(value));

In the above snippet, each subscribe() call returns a new Subscription, We can see from the output that the observables run twice, one for each subscription to return data for both of its subscribers at a same time.

Observable started.
Observable started.
1
1
2
2

Synchronous and Asynchronous

Lastly, while a Promise is asynchronous in nature, an Observable can be either synchronous or asynchronous.

A Promise is asynchronous which means on instantiating a Promise, execution does not happen right away but rather on the next event loop. On the other hand, once you have instantiated and subscribed to an Observable, the Observable runs right away. In this way, an Observable can provide a better performance.

As the below snippet and output demonstrate, even though we declare the Promise before the Observable, the output from the observable shows first before the output from the Promise.

import { interval, Observable, Subscription } from "rxjs";

const aPromise = new Promise((resolve, reject) => {
  resolve('Hi there. I\'m a Promise ');
});
aPromise.then(value => console.log(value));
const anObservable = new Observable((subscriber) => {
  subscriber.next('Hi there. I\'m an Observable');
})
anObservable.subscribe(value => console.log(value));

Below is the output:

Hi there. I'm an Observable
Hi there. I'm a Promise 

An observable can also be asynchronous. For example, we can wrap the .next() call in an asynchronous function such as setTimeout() to have the observables to emit the value asynchronously.

import { interval, Observable, Subscription } from "rxjs";

const aPromise = new Promise((resolve, reject) => {
  setTimeout(() => resolve('Hi there. I\'m a Promise '), 500);
});
aPromise.then(value => console.log(value));
const anObservable = new Observable((subscriber) => {
  setTimeout(() => subscriber.next('Hi there. I\'m an Observable'), 500);
})
anObservable.subscribe(value => console.log(value));

Below is the output:

Hi there. I'm a Promise 
Hi there. I'm an Observable

Since both the Promise and the Observable run asynchronously, and because we declare the promise first, the output from the promise shows up in the console before the output from the observable, as expected.

Conclusion

In this post, I have shown some of the differences between a Promise and an Observable. Both are great frameworks for producing and consuming data. However, from what I understand, an observable can do whatever a promise can and much more. Furthermore, an observable provides much greater flexibility over a promise as it is multicast and cancellable; both characteristics are missing in a Promise by default. Therefore, you should use an observable over a promise whenever possible.

References

Observable

Canceling Promises in JavaScript

Workshop: Reactive Fundamentals with RxJS Day 1

1 comment