Observables vs. Promises — Which One Should You Use?

Image by author

JavaScript is a single-threaded, non-blocking, and asynchronous concurrent language. That means that JavaScript’s engine doesn’t sit and wait for statements to finish. Instead, it moves to the next statement.

How can we rely on results from asynchronous functions? In the early stages, callbacks were the only available approach. However, they made the code hard to read, which lead to the well-known callback hell.

Observables and Promises were born to precisely fix that problem. Their implementations help us deal with that asynchronous code in a cleaner way. They have different APIs and their motivation is slightly different.

How do we know which one is right for us? Here we will see the difference between each implementation. That will help us choose the right tool for the right job.

As mentioned earlier, Promises and Observables have a completely different approach to dealing with async code. Here we will check the top four most notable differences.

To follow along with the examples, you can use the rxjs web dev Browser web console.

1. One value vs. multiple values

The biggest difference is that Promises won’t change their value once they have been fulfilled. They can only emit (reject, resolve) a single value. On the other hand, observables can emit multiple results. The subscriber will be receiving results until the observer is completed or unsubscribed from. Here is code to show you these differences:

That makes Observables a great tool for listening to streams of data. There is even a bidirectional kind of Observable: Subjects. A perfect use case for those are web sockets. The RxJS library ships with a thin wrapper on web sockets.

import { webSocket } from "rxjs/webSocket";

2. Observable subscriptions are cancellable; promises aren’t

Once you start a promise, you can’t cancel it. The callback passed to the Promise constructor will be responsible for resolving or rejecting the promise. The subscriber is passive; once fired, it can just react to the result.

Observables are less passive. Once a subscriber is created, it can opt out of the observer at any time. That makes them useful in scenarios where we are no longer interested in the response. For example, when a user leaves a page.

There are many ways to cancel/complete subscribers. Let’s check the three most common ones:

  • unsubscribe: manually canceling the subscription from the Observable
  • take: an operator for taking a number X of elements and canceling the subscription
  • takeUntil: an operator that keeps on taking values until the passed Observable emits any value.

Let’s see examples for each of the above:

The Observable’s operators are essential. They let us compose declaratively complex asynchronous operations. In the above example, it is clear how crucial and readable the take and takeUntil operators are.

Sometimes they are not as trivial to grasp. To better understand them, it’s worth looking at their marble graph representation. You can find all the operator’s representations here.

Let’s check at the take operator representation:

Capture from rxmarbles.com

We have a representation of the input and output of the operator streams.

3. Eager vs. lazy execution

There is a difference in how Observables and Promises are executed. Promises are executed eagerly whilst Observables are executed lazily. What does that mean?

Eagar: The Promise callback will execute right away at the constructor level.

Lazy: The Producer function will only trigger after there is a subscription created for that Observable. Otherwise, it will stay idle.

Let’s see examples for both:

We can see in the above example how the logged statement 1. Callback execution and 1. Execution of observable body happen in a different order.

4. Runtime execution

The ES Promises, once resolved, will queue the callback in the microtask queue. That means they will be executed after the current macro task has been completed.

Let’s see an example:

You can’t change the above behavior.

With Observables, you can fine-tune the runtime execution using schedulers. A scheduler controls when a subscription starts and when notifications are delivered.

  • null: by default notifications are delivered synchronously and recursively.
  • queueScheduler: schedules on a queue in the current event frame.
  • asapScheduler: schedules on the microtask queue. The same queue as promises use.
  • asyncScheduler: similar to scheduling a task using setInterval. It will be therefore scheduled in the macro task queue.
  • animationFrameScheduler: relies on requestAnimationFrame API.

Let’s see an example with asapScheduler:

Given that there are quite a few differences between them, can they work together? Do we have to choose between one of them? Absolutely not.

You can create Observables from promises and you can dump an Observable to a Promise. However, in the latter, as Promises take only one value, you would have to choose if you want the first or last value to be dumped to the Promise. The rxjs library provides a firstValueFrom and lastValueFrom for that particular use case.

Let’s see some examples:

We have seen the differences between both. When to use one or the other is a matter of preference and use case. Both are great at solving the async paradigm in JavaScript.

Be aware that Observables come at a cost. They are not natively supported by the Browser. The go-to library implementation is RxJS. Its bundle size can be up to 17.4kb. As it’s tree shakeable you will surely be looking at less than that. It is not a big bundle but Promises are natively supported. That means that you will be saving yourself the bundle’s Observable tax.

Cheers. If you liked what you read, check out this story below:


Leave a comment

Your email address will not be published. Required fields are marked *