Promises vs Observables
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
resolve function with
observer.next call and instead of
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:
- 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
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
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!
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:
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
In this particular case it would be more handy, if a Promise called function passed to the constructor separately for every user calling
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:
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
Let’s assume that in the previous example we actually want to call
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:
"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:
"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).
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!