ReactiveX/rxjs · error · TypeError

MarbleContext.setTimeout: callback must be a function

Error message

MarbleContext.setTimeout: callback must be a function

What it means

The TestScheduler's MarbleContext patches setTimeout to run inside the virtual clock; the callback must be a real function. Passing a string of code (legacy setTimeout('code()') semantics), an object, or undefined throws a TypeError rather than being queued.

Source

Thrown at packages/rxjs/src/testing/index.ts:35

const originalClearTimeout = globalThis.clearTimeout;
const originalSetInterval = globalThis.setInterval;
const originalClearInterval = globalThis.clearInterval;

const flushMicrotasks = Promise.resolve();

export class FakeTimers {
  #timerId = 0;
  #timerQueue: TimerQueueItem[] = [];

  #shouldUseNodeTimeout = typeof globalThis.setTimeout(() => {}) === 'object';

  #setTimeout: typeof globalThis.setTimeout = (() => {
    const patched = (callback: TimerHandler, delay = 0, ...args: any[]): any => {
      const id = ++this.#timerId;
      const time = this.#now + delay;

      if (typeof callback !== 'function') {
        throw new TypeError('MarbleContext.setTimeout: callback must be a function');
      }

      const item: TimerQueueItem = { id, callback: callback as (...args: any[]) => void, delay, time, type: 'timeout', args };
      this.#addTimer(item);

      if (this.#shouldUseNodeTimeout) {
        let ref = false;
        const nodeTimeout = {
          ref: () => {
            ref = true;
            return nodeTimeout;
          },
          unref: () => {
            ref = false;
            return nodeTimeout;
          },
          hasRef: () => {
            return ref;

View on GitHub (pinned to 54796b38a5)

Solutions

  1. Always pass a function: setTimeout(() => doThing(), 100)
  2. Fix optional-callback bugs so undefined is never forwarded to setTimeout
  3. Assert callback type in test setup if the code under test receives timers from untrusted input

Example fix

// before
setTimeout('console.log(1)', 100);
// after
setTimeout(() => console.log(1), 100);
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof callback !== 'function') throw new TypeError('callback must be a function');
setTimeout(callback, delay);

Type guard

const isFn = (cb: unknown): cb is (...args: any[]) => void => typeof cb === 'function';

Prevention

When it happens

Trigger: Within a TestScheduler.run(...) callback calling setTimeout('doThing()', 100), or passing a non-function callback such as setTimeout(null, 10) or forwarding a possibly-undefined handler.

Common situations: Legacy browser-style code using string callbacks, or application code under test that conditionally calls setTimeout(cb) where cb can be undefined due to an optional callback bug.

Understand the failure class

Related errors


AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28). Data as JSON: /api/errors/6f54a6fdc28eb69e. Report an issue: GitHub.