Promise vs Observable#
Let's break down the key differences between Observables (from RxJS) and Promises (native JavaScript). While both deal with asynchronous operations, they have fundamental differences in how they work and what they're capable of.
Here's a comparison table and explanations:
| Feature | Promise | Observable |
|---|---|---|
| Values Emitted | One value (or one rejection) | Multiple values over time (or error/complete) |
| Execution | Eager: Starts immediately upon creation | Lazy: Starts only when subscribed to |
| Cancellable? | No (standard Promises aren't cancellable) | Yes (via Unsubscription) |
| Operators | Limited (.then(), .catch(), .finally()) |
Rich set of operators (map, filter, retry, etc.) |
| Use Cases | Single async events (HTTP requests, timers) | Streams of events, complex async flows, state |
| Primary Library | Native JavaScript ES6+ | RxJS library (often used with Angular) |
Explanation of Differences#
-
Single vs. Multiple Values:
- Promise: Designed to handle a single asynchronous event that will eventually succeed (resolve with a value) or fail (reject with an error). Once a Promise settles (resolves or rejects), it's done. It will never emit another value.
- Analogy: Ordering a specific package online. You wait, and eventually, you get that one package (resolve) or a notification it couldn't be delivered (reject). The transaction is then over.
- Observable: Represents a stream or sequence of values arriving over time. It can emit zero, one, or multiple values. It can also signal an error or completion.
- Analogy: Subscribing to a newsletter or a YouTube channel. You might receive multiple emails/videos over days or weeks (multiple
nextemissions). You could also get an error notification, or the channel might eventually stop publishing (complete).
-
Eager vs. Lazy Execution:
- Promise: A Promise starts executing its asynchronous operation the moment it is created. Calling
.then()doesn't trigger the operation; it just registers what to do when the already-running operation finishes. - Example:
const myPromise = new Promise(/* executor starts now */); - Observable: An Observable is lazy. The code inside it (e.g., the function making an HTTP call) doesn't run until someone actually subscribes to it using
.subscribe(). Each subscription typically triggers a new, independent execution. (Operators likeshareReplaymodify this to share executions). - Example:
const myObservable = new Observable(/* code here runs only on subscribe */); myObservable.subscribe(); // Execution starts now.
- Promise: A Promise starts executing its asynchronous operation the moment it is created. Calling
-
Cancellable:
- Promise: Standard Promises don't have a built-in
.cancel()method. Once you create a Promise and its operation starts, there's no standard way to tell it, "Stop what you're doing, I don't need the result anymore." (Though browser APIs likeAbortControllercan sometimes be used to cancel the underlying operation, like anfetchrequest, which then causes the Promise to reject). - Observable: Observables are cancellable via their
Subscriptionobject. When yousubscribe(), you get back aSubscription. Callingsubscription.unsubscribe()signals to the Observable that the subscriber is no longer interested. This often triggers cleanup logic within the Observable (like clearing intervals or cancelling HTTP requests viatakeUntilDestroyedor similar mechanisms) and stops further emissions to that subscriber.
- Promise: Standard Promises don't have a built-in
-
Operators:
- Promise: Has basic chaining with
.then()(for success),.catch()(for error), and.finally()(for cleanup). You can also usePromise.all(),Promise.race(), etc., for combining promises. - Observable: Comes with the vast RxJS library of operators (
map,filter,reduce,retry,retryWhen,debounceTime,switchMap,mergeMap,combineLatest,withLatestFrom,shareReplay,timeout,find,delay,tap, etc.). These operators allow for powerful manipulation, combination, and control of asynchronous data streams in a declarative way.
- Promise: Has basic chaining with
When to Use Which (General Guidelines)#
Use Promises when:
- You're dealing with a single, one-off asynchronous operation (like a typical HTTP GET or POST request where you expect one response).
- You're working with native browser APIs or libraries that return Promises.
- The complexity doesn't warrant pulling in the full RxJS library.
Use Observables when:
- You need to handle streams of data arriving over time (WebSocket messages, user input events like keystrokes or mouse movements, repeated interval emissions).
- You need the advanced transformation, combination, or control capabilities provided by RxJS operators (retrying, debouncing, throttling, complex filtering/mapping, etc.).
- You need the ability to cancel asynchronous operations cleanly.
- You are working heavily within the Angular framework, which uses Observables extensively (e.g.,
HttpClient,Routerevents,EventEmitter).
In modern Angular, while you can use Promises, Observables are generally preferred for asynchronous operations, especially when dealing with framework features or requiring more complex stream manipulation, due to their power, flexibility, and cancellable nature.
Converting Between Them#
- Promise to Observable:
from(promise). Remember the promise is still eager and non-cancellable underneath. - Observable to Promise:
firstValueFrom(obs$)resolves with the first emission;lastValueFrom(obs$)waits for completion and resolves with the final value. Both reject if the source errors, or completes without a value. The old.toPromise()method is deprecated and removed in current RxJS; mentioning its replacements is an easy interview win.
Interview Q&A#
What are the three biggest differences between a Promise and an Observable?
Value count (one settle vs many emissions), execution model (eager on creation vs lazy per subscription), and cancellation (none vs unsubscribe-driven teardown). Operators and multicasting come right behind as follow-ups.
How do you convert an Observable to a Promise in current RxJS, and what happened to toPromise()?
firstValueFrom and lastValueFrom. toPromise() was deprecated and removed, partly because its behavior on empty completion (resolving with undefined) was ambiguous; lastValueFrom rejects with EmptyError instead, making the empty case explicit.
Can a Promise be cancelled?
Not by the Promise API itself. You can abort the underlying operation with AbortController (for fetch), which rejects the promise, but the promise abstraction has no unsubscribe. Observables build cancellation into the model: unsubscribing runs producer teardown, which is how HttpClient aborts requests under switchMap.
Next Up#
- Cold Observables: laziness and per-subscriber execution in depth
- from: bringing promises into RxJS pipelines
- switchMap: cancellation put to work