Promises vs Observables

Observables are on the rise. Angular 2 includes them as default async type, you can use them as middleware for your React-Redux app and there is even a proposal to add them to JavaScript itself. But why and when should you use them? This article goes in depth into what are the biggest differences between these two async types. Having such knowledge should help you make informed decisions about when and why use them both. Even if at the end you decide that Observables are not for you, hopefully you will still gain some additional knowledge about good old Promises.

Note that this article describes how Observables work in RxJS and in mentioned tc39 proposal. Results with other reactive programming libraries may vary.

Article assumes you know basics of Promises. On the other hand, if Observables are a total novelty to you, this article might work as a sort of an introduction. But certainly do not stop here, as there is much more to Observables than might seem after reading the article. In particular, we barely mention operators, which are the bread and butter of RxJS and reactive programming in general.

Single value vs multiple values

Promises are most commonly used to handle HTTP requests. In this model, you make a request and then wait for a single response. You can be sure that there won’t be multiple responses to the same request.

Promises actually enforce this semantics. You can create a Promise, which resolves with some value:

But attempting to resolve Promise again with another value will fail. Promise is always resolved with the first value passed to the resolve function and ignores further calls to it:

On the contrary, Observables allow you to resolve (or, as we say, “emit”) multiple values. Here is how it would look:

Note how similar the syntax is — we switched Promise to Observable, replaced resolve function with observer.next call and instead of then used subscribe, which behaves very similarly.

This behaviour is actually the biggest selling point of Observables. When you think about what are the sources of asynchrony are in the browser, you quickly realize that single request — single response model only works with simple HTTP requests or setTimeout calls. But there are still:

  • setInterval calls,
  • webSockets,
  • DOM events (mouse clicks etc.),
  • any kind of events, for that matter (also in Node.js).

Even though a while back we made some progress with Promises, there are still many things that we continue to implement with (rightly) dreaded callbacks. Didn’t someone realize that we only solved part of a problem? Thankfully authors of RxJS did.

Let’s for example see how wrapping setInterval in Observable would look like.

To not emit anonymous undefined value, we initiate i counter, and then every second we call observer.next method with its value.

This is an example of Observable that actually never stops emitting values. So instead of single value Promise, you get a type that might potentially emit any number of values!

Eager vs lazy

But let’s assume for a second that Promises do support emitting multiple values. Let’s rewrite setInterval example to utilize this imaginary Promise:

We have a problem here. Even though no one is listening for these numbers (we are not even logging them here), setInterval is still called immediately at the moment of Promise creation. We are wasting resources, emitting values that no one will listen to. This happens because Promise constructor immediately calls function passed to it. You can test it very simply with following code:

This will print "I was called!" to the console immediately. On the contrary test following, Observable based, code:

This time nothing happens. This is because while Promises are eager, Observables are lazy. Function passed to Observable constructor gets called only when someone actually subscribes to an Observable:

This seems like a small change, but let’s come back to our Observable wrapping setInterval:

Thanks to laziness, setInterval is not called at this point and even i variable is not initiated. The function passed to Observable just waits there until someone actually subscribes to an Observable.

To drive this point home: initialized Promise represents some process that has already started happening (HTTP request is already sent), and we are just waiting for resulting value. That’s because function starting that process gets called at the moment Promise is created. On the other hand, initialized Observable represents process that might start happening — it will start only when we actually subscribe, potentially saving browser resources from being wasted on work that nobody cares about.

Not cancellable vs cancellable

So let’s assume that Promises are lazy. Imagine that in our Promise based example we call setInterval only after someone starts listening for results (by calling then). But what about the case when someone stops listening at certain point? As you probably know, setInterval function returns token, which can be in turn used with clearInterval method to cancel setInterval continuously calling its callback. We should be able to do this, when consumer of a Promise no longer wants to listen to events, so that — again — resources are not waisted when no one listens.

Actually, some Promise libraries actually support this. Bluebird Promises support cancel method, which you can call on a Promise itself, to stop whatever happens inside it. Let’s see how we would use it, to cancel setInterval :

Note now we passed to onCancel function special callback, which will be called when user decides to cancel Promise. And cancellation itself looks like this:

Note that we cancel Promise that we got from then, which has side effect of logging values to the console.

This is exactly the behaviour Observables support. Let’s see how cancellation looks here:

Not many things changed in constructor, compared to cancellable Promise. Instead of passing function to onCancel, we just return it.

Cancelling (or, as we say, “unsubscribing”) Observable will also look similar:

It might seem that what is happening here is one-to-one equivalent to our cancellable Promise example — subscribe returns Observable, which gets unsubscribed.

But as a matter of fact subscribe doesn’t return Observable! This means you cannot chain several subscribe calls like you would chain then calls in Promises. subscribe returns a Subscription for given Observable. This Subscription has only one method — unsubscribe — which you can call, when you decide you don’t want to listen to certain Observable anymore.

If you are worried that lack of chaining makes Observables unusable, remember that there is whole world of so-called operators that we do not cover here. Operators do support chaining.

If on the other hand verbosity of handling this Subscriptions worries you, there exist operators that let you handle them in nice, declarative matter. As a matter of fact, under the hood every operator handles subscriptions in a smart way for you, making sure you do not subscribe to something you don’t need!

But enough advertisement, let’s continue. Even though there are Promise libraries that support cancellation, ES6 Promises do not. There was a proposal to add them to JavaScript, but it was rejected. There are still other ways to cancel your Promises, but if compare what still has a chance to land in a language itself, Observables definitely win here, since they where designed with cancellation in mind.

Multicast vs either unicast or multicast

But there is yet another problem with our imaginary, lazy Promise. Even if function passed to constructor was called only when someone called then on a Promise, what would happen if someone else would call then as well, few moments later? Should we call that function again? Or just share results from first call to every user?

Because Promises are eager, they naturally implement second solution — function passed to Promise constructor is called only when Promise is created and never again (unless you create brand new Promise with that function of course). This behavior works well for HTTP requests, where you do not want to double requests, even if many entities expect the results from that request. But consider simple Promise which can be used to defer some action one second:

Of course real Promise will start counting immediately, but if it was lazy, it would start counting only when someone would actually use it:

Function doSomething would be called after one second of waiting. Everything would be great, unless someone else decided to use the same Promise as well:

That person would naturally expect doSomethingElse to be called exactly one second from the moment it was passed to then, but in that case it would be called after half a second! Why? Because someone else used it before and when he/she used it, function passed to Promise constructor was called, thus calling setTimeout and starting one second countdown. By the time we call then with second function, timer is already halfway in it’s countdown. Since we share timer between both functions, they will be called at the same moment — second function half a second too fast.

Let’s modify our Promise by logging something in the function:

In previous example, even though then was called twice, you would see "I was called!" logged only once to the console, proving that there is only one instance of setTimeout clock.

In this particular case it would be more handy, if a Promise called function passed to the constructor separately for every user calling then. setTimeout would be then called for every user, ensuring that their callback will be called at exactly the moment they would expect. As a matter of fact, this is what Observable does. Let’s rewrite our Promise to an Observable:

Here every call to subscribe will start it’s own clock:

Both doSomething and doSomethingElse functions will be called one second from the moment they were passed to subscribe. If you look in the console, you will see "I was called!" printed to console twice, which shows that function passed to Observable constructor was indeed called twice and two instances of setTimeout timer were created.

But, as was mentioned, this is not a behaviour you always want. HTTP requests are great example of action that you want to perform only once and then share results between subscribers. Observables don’t do that by default, but in case of RxJS Observables you can make them do that very easily, by using share operator.

Let’s assume that in the previous example we actually want to call doSomething and doSomethingElse functions at the same time, no matter when they were passed to subscribe . Here’s what it would look like:

If Observable shares a result between many subscribers, we say it is “multicast”, since it casts single value to multiple entities. By default Observables are “unicast”, where every result will be passed to single, unique subscriber.

We thus see that Observable again win in flexibility: Promises (because of their eager nature) are always “multicast”, while Observable is “unicast” by default, but can be easily turned into a “multicast” Observable, if needed.

Always asynchronous vs possibly asynchronous

Let’s go back to much simpler example of a Promise:

Note that within a function we call resolve synchronously. Since we already have desired value, we immediately resolve a Promise with it. Surely this must mean, that when someone calls then on that Promise, callback function will be called immediately (synchronously) as well? Well… no. As a matter of fact, Promise ensures that callback passed to then will always be called asynchronously. It’s easy to see that with following code:

First "And now we are here." is logged and just then "5!" appears in the console, even though Promise was already resolved with that number.

Contrast this with an Observable, which actually may emit values synchronously:

This time "5!" appears first and just then we see "And now we are here.". Of course we could have deferred emitting a value, for example by wrapping observer.next(5) line in setTimeout. So again we see that Observables favour flexibility. You might actually argue that this behavior is dangerous, since subscribe does not work predictably, but I will just mention that in RxJS there are ways to enforce listening for events in asynchronous way (take a look at observeOn operator, if you are interested).

Conclusion

That’s it! Let me know in comments if you have some other good examples of how Promises and Observables differ from each other. How about similarities?

I hope that after reading this article you are now equipped to make informed decisions about which one to use in your project.

Protip: it might be both!