In RxJS, how does an Observable differ from a Promise in when work starts, how many values it delivers, and cancellation?
answer
- who decides when work begins
- one value versus many
- each subscribe runs the function again
- Subscription.unsubscribe() versus no cancel
basics
~20 sAn RxJS Observable is lazy: its producer runs only when subscribed, once per subscriber, and can deliver zero, one or many values synchronously or asynchronously. A Promise starts eagerly, settles once, and has no cancel; unsubscribe() stops an Observable's work.
solid answer
~40 sAn RxJS `Observable` is a lazy function: nothing runs until `subscribe()` is called, and each `subscribe()` runs the subscriber function again, so two subscribers mean two executions. It can push zero, one or many `next` values and then end with `error` or `complete` - or never end at all. Delivery can be synchronous, inside the `subscribe()` call itself. A `Promise` starts its work when it is created, settles exactly once, shares that one result with every `then()` caller, and always calls back asynchronously. The Promise has no cancel of its own, whereas `subscribe()` returns a `Subscription` whose `unsubscribe()` runs the Observable's teardown and stops the work. Convert with `firstValueFrom` or `lastValueFrom` when an `async` API needs a Promise.
code
ts · 30 linesimport { Observable } from 'rxjs';
const promise = new Promise<number>((resolve) => {
console.log('promise executor runs');
resolve(1);
});
const numbers$ = new Observable<number>((subscriber) => {
console.log('subscriber function runs');
subscriber.next(1);
subscriber.next(2);
subscriber.complete();
});
console.log('before subscribe');
numbers$.subscribe((n) => console.log('A got', n));
numbers$.subscribe((n) => console.log('B got', n));
console.log('after subscribe');
promise.then((n) => console.log('promise gave', n));
// promise executor runs
// before subscribe
// subscriber function runs
// A got 1
// A got 2
// subscriber function runs
// B got 1
// B got 2
// after subscribe
// promise gave 1go deeper
Recall the headline differences: an Observable is lazy and can emit many values, a Promise is eager and settles once, and only the Observable can be cancelled with unsubscribe().
Explain per-subscriber execution and synchronous delivery with a small logging example, and show firstValueFrom or lastValueFrom as the RxJS 7 bridge to async code instead of toPromise().
Connect the model to production bugs: duplicated requests from multiple subscriptions, work that keeps running because nobody unsubscribed, and promises that never settle because a stream never emits.
Frame when a team should expose Promises versus Observables at API boundaries: one-shot results read more simply as Promises, while streams that change or need cancelling justify RxJS's extra concepts.
## The two abstractions side by side A **Promise** and an RxJS **Observable** both let code react to a value that is not available yet, which is why they get compared in almost every RxJS screening. They differ along four axes that matter in practice: **when the work starts**, **how many values arrive**, **whether delivery is synchronous**, and **whether the work can be cancelled**. | Axis | `Promise` | RxJS `Observable` | |---|---|---| | Start | Eager: the executor runs as soon as `new Promise()` is called | Lazy: the subscriber function runs only when `subscribe()` is called | | Executions | One, shared by every `then()` | One per `subscribe()` call | | Values | Exactly one fulfilment or one rejection | Zero to many `next` values, then optionally `error` or `complete` | | Delivery | Callbacks always run asynchronously | Synchronous or asynchronous, whatever the producer does | | Cancel | No built-in cancel | `Subscription.unsubscribe()` runs the teardown | ## Laziness and per-subscriber execution The RxJS documentation describes an Observable as a **lazily evaluated computation**, closer to a function than to a value. Creating one with `new Observable(subscriber => { ... })` only stores the **subscriber function**. Calling `subscribe()` is what invokes it, with a fresh `Subscriber` for that call. - If nobody subscribes, the function never runs, so no request is sent and no listener is added. - If three consumers subscribe, the function runs three times, each run with its own state. - A Promise, by contrast, has already started by the time you hold it, and every `then()` reads the same settled result. That per-subscriber behaviour is the default for Observables built with the constructor. Sharing one execution between consumers is a deliberate opt-in through RxJS's subjects and sharing operators, which are a separate topic. ## Many values, and a terminal signal An Observable's producer talks to an **Observer** with three callbacks: 1. `next(value)` - zero or more times, one per value. 2. `error(err)` - at most once, and it ends the execution. 3. `complete()` - at most once, and it ends the execution. After `error` or `complete`, further notifications are ignored by the `Subscriber`. An Observable may also **never** terminate: a stream of clicks or of position updates keeps emitting until someone unsubscribes. A Promise has no equivalent of a second value, and it has no state that means "still running and will send more". ## Synchronous delivery is allowed A common misconception is that Observables are inherently asynchronous. They are not. If the subscriber function calls `subscriber.next(1)` straight away, the value reaches the observer **before `subscribe()` returns**. Code that logs before and after the `subscribe()` call will see the value printed in between. Promise callbacks, on the other hand, are always queued and run after the current synchronous code, even when the Promise is already resolved. ## Cancellation through the Subscription `subscribe()` returns a `Subscription`. Calling its `unsubscribe()` method: - marks the subscription `closed`, so later notifications from the producer are dropped; - runs the **teardown** that the subscriber function returned, for example clearing a timer or removing an event listener; - does **not** call the observer's `complete` callback - cancelling is not completing. A Promise offers nothing like this. You can ignore its result, and some underlying APIs accept an abort signal, but the Promise object itself cannot be told to stop. ## Crossing the boundary When an `async` function needs a value from an Observable, RxJS 7 provides two functions: - `firstValueFrom(source$)` resolves with the first value and then unsubscribes. - `lastValueFrom(source$)` resolves with the last value once the source completes. Both reject with the source's error, and both reject with `EmptyError` if the source completes without emitting, unless a `{ defaultValue }` config is passed. The older `toPromise()` method is deprecated in RxJS 7 and scheduled for removal in v8. Going the other way, RxJS's `from()` creation function wraps a Promise as an Observable. ## How to answer in an interview Lead with laziness and multiplicity, then cancellation. A strong answer adds that an Observable can emit synchronously, that each subscription is an independent execution unless sharing is added, and that unsubscribing tears the work down rather than merely ignoring the result. Mentioning that a Promise is the right tool for a single one-shot result, and an Observable for streams that change or need cancelling, shows judgment rather than recitation.
- Does calling unsubscribe() on an RxJS subscription trigger the observer's complete callback?No. `unsubscribe()` closes the subscription and runs the teardown the subscriber function returned, but the observer's `complete` callback is not called. Completion is a signal the producer sends when it has finished; unsubscribing is the consumer walking away. Code that must run on either ending belongs in the teardown or in a `finalize` step, not in `complete`.
- Can you await an RxJS Observable directly with the await keyword?No. An `Observable` is not a thenable, so `await source$` simply evaluates to the Observable object itself without subscribing. Use `firstValueFrom(source$)` when you want the first value, or `lastValueFrom(source$)` when the source completes and you want its final value.
A Promise is a parcel already dispatched: it arrives once, whether or not you still want it. An Observable is a subscription service that only starts sending issues once you sign up, sends as many as it has, and stops when you cancel.
saying these in an interview costs you the question
- Observables are always asynchronous, so values arrive after subscribe() returns.
- Creating an Observable starts its work, just like creating a Promise.
- Two subscribers to an Observable built with new Observable() share one execution by default.
- Unsubscribing calls the observer's complete callback.
- An Observable is just a Promise that can resolve several times.
- You can await an Observable directly to get its value.