In RxJS, why does first() error with EmptyError when the source completes without a value, while take(1) simply completes?
answer
- what completion without values means
- take(1) plus an emptiness check
- throwIfEmpty vs defaultIfEmpty
- second argument is the default
basics
~20 sfirst() promises one value: filter plus take(1) plus a check that errors with EmptyError if the source completes empty. take(1) promises at most one and just completes. first(pred, fallback) emits the fallback instead of erroring.
solid answer
~40 sIn RxJS 7.8, `first(predicate?, defaultValue?)` is built as an optional `filter`, then `take(1)`, then either `defaultIfEmpty(defaultValue)` or `throwIfEmpty(() => new EmptyError())`. So both operators emit the first (matching) value and complete, but when the source **completes** with nothing, `take(1)` completes quietly while `first()` errors with `EmptyError` ("no elements in sequence"). Supplying a second argument switches to the default: `first(r => r.celsius > 90, null)` emits `null`. Emptiness is only detected on completion — on a source that never completes, both just wait. `last()` is the mirror image, built on `takeLast(1)`, and `single()` is stricter still. Choose `first()` when an empty source is a bug you want surfaced, `take(1)` when empty is a normal outcome.
code
ts · 16 linesimport { EMPTY, take, first } from 'rxjs';
EMPTY.pipe(take(1)).subscribe({
next: v => console.log('take next', v),
complete: () => console.log('take complete'),
});
// take complete
EMPTY.pipe(first()).subscribe({
next: v => console.log('first next', v),
error: e => console.log('first error', e.name),
});
// first error EmptyError
EMPTY.pipe(first(null, 0)).subscribe(v => console.log('first default', v));
// first default 0go deeper
Recall that both end after one value, but only first() errors with EmptyError when the source completes without one.
Explain first() as filter plus take(1) plus throwIfEmpty or defaultIfEmpty, and the second-argument default detected by argument count.
Trace an EmptyError back to an upstream filter or catchError returning EMPTY, and decide per call site whether empty is a bug or a normal outcome.
Set a convention for when absence should be an error versus a value, so services and components handle empty results consistently.
## Two operators, two promises Both operators end a stream after its first value, which is why they look interchangeable. They differ in **what they promise when there is no first value**: - `take(1)` promises **at most one** value. Zero values is a legitimate outcome, so it forwards the completion. - `first()` promises **exactly one** value. Completing without one breaks the promise, so it errors. The RxJS 7.8 source makes this literal. `first(predicate, defaultValue)` returns a pipe of: 1. `filter(...)` when a predicate is given; 2. `take(1)`; 3. `defaultIfEmpty(defaultValue)` **if a second argument was passed**, otherwise `throwIfEmpty(() => new EmptyError())`. `EmptyError` is exported from `'rxjs'`; its `name` is `'EmptyError'` and its message is `'no elements in sequence'`. ## The outcomes side by side | source behaviour | `take(1)` | `first()` | `first(p, fallback)` | `last()` | |---|---|---|---|---| | emits values, then completes | first value, complete | first value, complete | first match, complete | final value, complete | | completes with no values | complete | `EmptyError` | `fallback`, complete | `EmptyError` | | never completes, no value | waits | waits | waits | waits | | never completes, has values | first value, complete | first value, complete | first match, complete | never emits | Two consequences follow: - **Emptiness is a completion-time fact.** `first()` does not error because a value is slow; it errors only when the source says it is finished. - **`last()` needs completion to emit at all.** On an endless stream such as `interval()` it waits forever. ## The default value, precisely The default is detected by **argument count** (`arguments.length >= 2`), not by whether the value is `undefined`. So `first(null, undefined)` has a default of `undefined` and emits it instead of erroring. Pass `null` as the predicate to use a default without filtering: `first(null, 0)`. With a predicate, the default is emitted when the source completes without a match — for example, a finite batch of sensor readings in which none exceeds the alarm threshold: ```ts import { from, first } from 'rxjs'; from([72, 81, 88]).pipe(first(c => c > 90, null)) .subscribe(v => console.log(v)); // null ``` Without the `null`, the same pipeline errors with `EmptyError`. ## single: exactly one, and no more `single(predicate?)` waits for completion and then checks the count: - no values at all: `EmptyError`; - values, but none matched: `NotFoundError` ("No matching values"); - a second match: `SequenceError` ("Too many matching values"), raised as soon as it arrives; - exactly one match: emitted at completion. ## Choosing in practice 1. Use **`take(1)`** when an empty source is normal — a lookup that may find nothing, a stream that may end before producing. 2. Use **`first()`** when an empty source is a bug and you want it to surface as an error rather than as a silent missing value. 3. Use **`first(p, fallback)`** when there is a sensible fallback and you want the stream to always produce exactly one value. 4. Remember that `first()` **completes the stream**, so any error handling for `EmptyError` belongs downstream of it. ## In an Angular codebase The choice shows up constantly around one-shot reads: - An `HttpClient` request observable emits one response and completes, so appending `take(1)` changes nothing, and appending `first()` changes nothing either unless the response stream were ever empty. - A long-lived state stream (a `BehaviorSubject` in a service, a form control's `valueChanges`) never completes on its own, so `take(1)` and `first()` behave identically there: they read the next value and complete. - The difference appears only on **finite streams that can be empty** — a filtered batch, a lookup that returns `EMPTY` when nothing is found. In tests, assert the empty case explicitly: subscribe with an `error` callback and check `err instanceof EmptyError`, or use a marble test that expects an error at completion. ## Where EmptyError comes from in practice The classic surprise: an upstream `catchError(() => EMPTY)` or a `filter` that removes everything turns a working `first()` into an `EmptyError` that appears far from its cause. `firstValueFrom` and `lastValueFrom` raise the same `EmptyError` on an empty source, which is a separate API.
- Does first(null, undefined) error on an empty source?No. RxJS decides whether a default exists from the number of arguments, not from the value, so passing `undefined` explicitly counts as a default and the stream emits `undefined`, then completes.
- What does single() deliver for a stream that emits 3 and 7 and then completes?A `SequenceError` with the message "Too many matching values". `single()` errors as soon as a second matching value arrives; it emits only when exactly one value matched and the source completed.
- Why does last() never emit on interval(1000)?`last()` is built on `takeLast(1)`, which can only know which value was last once the source completes. `interval` never completes, so the operator keeps waiting and emits nothing.
saying these in an interview costs you the question
- first() and take(1) are identical in every case.
- first() errors if no value arrives within a timeout.
- Passing undefined as the default is the same as passing no default.
- take(1) errors with EmptyError when the source completes empty.
- last() emits the most recent value on a stream that never completes.