Do Observables replace Promises?
TL;DR they don’t.
It’s been quite a while since I adopted RxJS and fell in love with the idea of Reactive Programming. It takes time and quite a bit of practice to really get to know reactive programming and learn to appreciate it. When you start playing with RxJS one of the first things you would notice is the similarities of an Observable to a Promise. The next thing you would do is google “promise vs observable” and the internet will explain to you what the differences are quite well.
I’m baffled by the number of people I come across who think observables are a replacement for promises. I guess we have Angular to partly blame for that. Despite Angular playing an important role in the fame and adoption of RxJS, it did play a part in this misconception with its use of Observables for use-cases where Promises are more applicable.
Here is a breakdown of the fundamentals of promise and observables. These are what you will find in most “promise vs observable” type articles.
- One value — a promise will resolve to only one value (or reject). Once resolved its value will not change.
- Eager — a promise is already on its way to resolve. The underlying functionality that affects the result of your promise is already in progress.
- Has value — a promise will always resolve to a value (or reject) and that value will never change (immutable), regardless of when you listen to the promise.
- Async — a promise is always asynchronous. Even when you use Promise.resolve(“done”) to create a promise it does not resolve to the value “done” synchronously.
- Not cancellable — synonymous to the point 2, you cannot affect the underlying functionality that resolves/rejects the promise, thus you cannot cancel it.
- Multicast — any number of consumers can listen to the same promise (by calling then). They will all be listening to the same source and would receive the same result at the same time (each then callback will fire in the order they were registered).
- Limited Operations — promises come with the predefined set of native features, which enable you to do quite a few things including combine, chain, or race promises. You can also map promise values using then. A lot can be done using these.
- One or more values — an observable can emit one or multiple values. You can only know how many values an observable will emit once it completes.
- Eager or lazy—this depends on the Temperature of the observable. The underlying functionality that affects the observable can already be in progress (with a Hot observable) OR could only be started by subscribing to it (with a Cold observable).
- May have value — the behaviour depends on the Temperature of the observable. If the observable is eager (hot), then you will only receive values emitted after your subscribe and until you unsubscribe. If the observable is lazy (cold), it only emits after you subscribe up until you unsubscribe.
- Sync or async — observables created using some creation operators are synchronous (ex: range(1, 5), of(‘jump’), from([9,8]) etc), while others are asynchronous (ex: interval(100), fromEvent() etc). When you use the create operator you can make it either a sync or async observable.
- May be cancellable — just as in point 2 this depends on the Temperature of the observable. Cold observables can cancel the underlying functionality just as they can start them. However the cancel-lability of a Hot observable is subjective. See below for more.
- Unicast or multicast — again depends on the Temperature of the observable. Hot observables will multicast > meaning all subscribers of a hot observable are listening to the same stream. Whereas cold observables will unicast > meaning each subscriber starts a new stream and will be the only listener of that stream.
- Operators — RxJS provides powerful methods with its constructs that enable you do pretty much anything with your Observables and their output. See docs.
If you want to understand more about how Promises differ to Observables, read this article by Mateusz Podlasin. To understand more about Observable Temperature and how different observables behave, read my post on Cancelling Observables.
Use Cases — Observable or Promise
Let’s look at a few use-cases and describe their requirements using above features to define the best construct to use.
A simple http GET or POST call to an end point.
- Requirements — One response, Eager, always async, not cancellable, must not lose response, multicast.
- Conclusion — Promise is a clear winner.
- Rationale — We may argue that a lazy observable is useful because the consumer can decide when to make the http call (by subscribing). However this would also mean the observable would hide the details of the http call within (endpoint, method, params etc) and allow a consumer to only make the call. Also such observable will make http calls every time someone subscribes. All of this is quite unnatural to the requirement of making an HTTP call.
Listening to UI events on the front end.
- Requirements — Multiple events, eager, async, not cancellable, may emit while not listening, multicast, operators can be useful.
- Conclusion — Hot Observable.
- Rationale — Since the nature of the functionality is having multiple events, Promise is not a good option. The fact that the event generation is not controlled by the consumer makes this a clear case of a Hot Observable.
Receive WebSocket Messages
Listening to messages received on a websocket connection.
- Requirements — Multiple messages, eager or lazy, async, cancellable, ideally don’t want to lose any messages, unicast or multicast, operations can be useful.
- Conclusion — Warm Observable ( 🤯 WTF?). Cold Observable and Hot Observable are also applicable.
- Rationale — Ideal implementation is to have Cold Observable that would open the websocket during the first subscribe and then convert into a Hot Observable (eager, multicast, not cancellable). The Observable would turn cold after everyone unsubscribes (by making it cancellable). This is a form of a Warm Observable. See my post on Cancelling Observables for more on this.
Read Full File
Read a full file on disk. A typical fs.readFile type thing that would return you the contents of the file.
- Requirements — One value of file contents, eager or lazy, async, may be cancellable, always should contain same contents, multicast.
- Conclusion —Ideally a Promise.
- Rationale — While Promise might be ideal here there are some rare cases we may make use of features such as cancelling the file read or even starting the file read lazy. Honestly I cant think of any valid use cases these requirements off the top of my head.
Here we discuss few cases of how Promises and Observables add value quite clearly. The goal of this post has been do define how Promises are still a valid construct possibly in many use-cases. Have any thoughts about this? feel free to drop a comment.
Keep rocking asynchronously!