skip to content

In RxJS, why does first() error with EmptyError when the source completes without a value, while take(1) simply completes?

level: middleimportance: must knowfreq 60%

answer

  1. what completion without values means
  2. take(1) plus an emptiness check
  3. throwIfEmpty vs defaultIfEmpty
  4. second argument is the default

basics

~20 s

first() 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 s

In 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 lines
ts
import { 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 0

go deeper

for a junior

Recall that both end after one value, but only first() errors with EmptyError when the source completes without one.

for a middle

Explain first() as filter plus take(1) plus throwIfEmpty or defaultIfEmpty, and the second-argument default detected by argument count.

for a senior

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.

for a principal

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.