What are the differences between observables and promises in JavaScript?

Viewed 22644

So i've read that observables are looking to overtake promises in terms of usage in some of upcoming JavaScript MVC's:

What is the difference between observables and promises?

Updated: Apologies! removed my falsy statement.

7 Answers

Observables are often compared to promises. Here are some key differences:

Observables are declarative; computation does not start until subscription. Promises execute immediately on creation. This makes observables useful for defining recipes that can be run whenever you need the result.

Observables provide many values. Promises provide one. This makes observables useful for getting multiple values over time.

Observables differentiate between chaining and subscription. Promises only have .then() clauses. This makes observables useful for creating complex transformation recipes to be used by other part of the system, without causing the work to be executed.

Observables subscribe() is responsible for handling errors. Promises push errors to the child promises. This makes observables useful for centralized and predictable error handling.

Best explanation by angular on official website :

https://angular.io/guide/comparing-observables

Observables VS. Promises (by Jeremy Vilken)

In addition to new syntax, observables are a newer pattern for JavaScript applications to manage asynchronous activities. They’re also a draft for a feature to be natively implemented in the JavaScript language so it has weight behind the pattern. RxJS is the library we’ll use to help us implement observables in our applications.

Promises are another construct to help deal with asynchronous calls, which are useful for making API requests, for example. Promises have a major limitation in that they’re only useful for one call cycle. For example, if you wanted to have a promise return a value on an event like a user click, that promise would resolve on the first click. But you might be interested in handling every user click action. Normally, you’d use an event listener for this, and that allows you to handle events over time. This is an important distinction: Observables are like event handlers in that they continue to process data over time and allow you to continuously handle that stream of data.

When you understand Observables correctly, the differences to Promises are pretty obvious.

The best way to demystify a convoluted concept is to implement it from scratch. Here is an almost purely functional Observable implementation and an example, that wouldn't work with Promises:

/*** Observable type ***/

// type constructor (of a product type)

const proType = name => cons => {
  const f = (k, ...args) =>
    Object.defineProperties({["run" + name]: k}, {
      [Symbol.toStringTag]: {value: name},
      [Symbol("args")]: {value: args}
    });

  return cons(f);
};

// value constructor

const Observable = proType("Observable")
  (Observable => k => Observable(k));

/*** Observer factory ***/

const Observer = observer => {
  let isUnsubscribed = false;

  return {
    next: function(x) {
      if (isUnsubscribed)
        throw new Error("unsubscribed");

      else {
        try {
          return observer.next(x);
        }

        catch(e) {
          isUnsubscribed = true;
          this.cancel();
          throw e;
        }
      }
    },

    error: function(e) {
      if (isUnsubscribed)
        throw new Error("unsubscribed");

      else {
        try {
          return observer.error(e);
        }

        catch(e_) {
          isUnsubscribed = true;
          this.cancel();
          throw e_;
        }
      }
    },

    complete: function() {
      if (isUnsubscribed)
        throw new Error("unsubscribed");

      else {
        try {
          const r = observer.complete();
          this.cancel();
          return r;
        }

        catch(e) {
          isUnsubscribed = true;
          cancel();
          throw e;
        }
      }
    }
  };
};

/*** combinators + auxiliary functions ***/

const subscribe = observable => handlers => {
  const observer = Observer(handlers),
    cancel = observable.runObservable(observer);

  observer.cancel = cancel;
  return cancel;
};

const obsMap = f => observable =>
  Observable(observer => {
    const mapObserver = {
      next: x => observer.next(f(x)),
      error: e => observer.error(e),
      complete: () => observer.complete()
    };

    return observable.runObservable(mapObserver);
  });

/*** main ***/

// create an Observable instance

const numStream = Observable(observer => {
  let i = 0;

  const timer = setInterval(() => {
    observer.next(i++);
  }, 1000);
  
  return () => clearTimeout(timer);
});

// map a function over it

const squaredNumStream =
  obsMap(x => x * x) (numStream);

// run the observable

const cancel = subscribe(squaredNumStream) ({
  next: x => console.log(x),
  error: e => console.error(e),
  complete: () => console.log("finished")
});

// cancel it

setTimeout(cancel, 11000);

In the example above the Observable squaredNumStream emits a stream of theoretically infinite values asynchronously. You cannot do this with Promises, because they represent a single future value.

I could have easily subscribed to another squaredNumStream without both instances interfering with each other. This is because Observables are unicast, whereas Promises are multicast.

squaredNumStream doesn't run at declaration time, but only after subscription, because Observables are lazily evaluated. Promises on the other hand are eagerly evaluated that is, they start running as soon as you create them.

And finally, Observables are cancelable by design, whereas Promises are hard to cancel due to there unicast semantics.

Related