sindresorhus/is · error · TypeError

Expected value which is `Observable`, received value of type

Error message

Expected value which is `Observable`, received value of type `${is(value)}`.

What it means

Thrown by `assert.observable()` when the value does not conform to the Observable contract — `isObservable` checks for the `Symbol.observable` (or `@@observable`) interop point per the TC39 Observable proposal. It narrows to `ObservableLike` so code can safely subscribe.

Source

Thrown at source/index.ts:1824

	}
}

export function assertNumericString(value: unknown, message?: string): asserts value is `${number}` {
	if (!isNumericString(value)) {
		throw new TypeError(message ?? typeErrorMessage('string with a number', value));
	}
}

// eslint-disable-next-line @typescript-eslint/no-restricted-types
export function assertObject(value: unknown, message?: string): asserts value is object {
	if (!isObject(value)) {
		throw new TypeError(message ?? typeErrorMessage('Object', value));
	}
}

export function assertObservable(value: unknown, message?: string): asserts value is ObservableLike {
	if (!isObservable(value)) {
		throw new TypeError(message ?? typeErrorMessage('Observable', value));
	}
}

export function assertOddInteger(value: number, message?: string): asserts value is number {
	if (!isOddInteger(value)) {
		throw new TypeError(message ?? typeErrorMessage('odd integer', value));
	}
}

export function assertPlainObject<Value = unknown>(value: unknown, message?: string): asserts value is Record<PropertyKey, Value> {
	if (!isPlainObject(value)) {
		throw new TypeError(message ?? typeErrorMessage('plain object', value));
	}
}

export function assertPositiveInteger(value: unknown, message?: string): asserts value is number {
	if (!isPositiveInteger(value)) {
		throw new TypeError(message ?? typeErrorMessage('positive integer', value));

View on GitHub ↗ (pinned to 7821031c66)

Solutions

  1. Verify the value comes from a library implementing `Symbol.observable` (RxJS does); convert Promises with `from(promise)` (RxJS) first.
  2. Deduplicate rxjs in your lockfile (`npm dedupe` / resolutions) so all code shares one Symbol.observable registration.
  3. If the source only exposes `subscribe`, wrap it: `new Observable(sub => source.subscribe(sub))`.
  4. Import `symbol-observable` polyfill early if targeting environments without the symbol.

Example fix

// before
assert.observable(somePromise); // throws: received 'Promise'
// after
import {from} from 'rxjs';
const obs = from(somePromise);
assert.observable(obs);
Defensive patterns

Strategy: type-guard

Validate before calling

if (value == null || typeof value.subscribe !== 'function') {
  throw new TypeError('Expected an Observable (object with subscribe and Symbol.observable)');
}

Type guard

function isObservable(value: unknown): value is {subscribe: Function} {
  return value != null &&
    typeof (value as any).subscribe === 'function' &&
    typeof (value as any)[Symbol.observable ?? '@@observable'] === 'function';
}

Try / catch

try {
  ow(value, ow.observable);
} catch (error) {
  if (error instanceof ArgumentError) {
    throw new TypeError(`Expected an Observable stream: ${error.message}`);
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling `assert.observable(value)` with a Promise, an RxJS Subject that lost its symbol through serialization, a plain object with only a `subscribe` method but no `Symbol.observable`, or an observable from a library/version that doesn't implement the interop symbol.

Common situations: Mixing RxJS versions in one dependency tree (two rxjs copies, each with its own symbol polyfill); passing a Promise where an Observable is expected; using a lightweight event-emitter that doesn't implement `Symbol.observable`; missing `symbol-observable` polyfill in older environments.

Related errors


AI-assisted analysis of sindresorhus/is@7821031c66 (2026-07-31). Data as JSON: /data/errors/c7a1540c96bdc8fe.json. Report an issue: GitHub ↗.