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
- Always pass a function: setTimeout(() => doThing(), 100)
- Fix optional-callback bugs so undefined is never forwarded to setTimeout
- 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
- Never pass strings to setTimeout inside TestScheduler.run
- Type callback params as functions, not TimerHandler, in code under test
- Guard optional callbacks before forwarding to timers
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28).
Data as JSON: /api/errors/6f54a6fdc28eb69e.
Report an issue: GitHub.