denoland/deno · error · TypeError
ERR_ILLEGAL_CONSTRUCTOR
ERR_ILLEGAL_CONSTRUCTOR
Error message
Illegal constructor
What it means
The Scheduler class exported by node:timers implements the Scheduling API, but there is exactly one scheduler per realm: Node and this Deno polyfill both make `new Scheduler()` throw ERR_ILLEGAL_CONSTRUCTOR. The class is exported only for identity checks such as `scheduler instanceof Scheduler`. All functionality lives on the exported `scheduler` singleton, whose methods also reject foreign `this` values with ERR_INVALID_THIS.
Source
Thrown at ext/node/polyfills/timers.ts:371
const promises = {
setTimeout: setTimeoutPromise,
setImmediate: setImmediatePromise,
setInterval: setIntervalAsync,
};
// There is exactly one `scheduler`, so identity is the check: a
// prototype-linked object, or one carrying whatever properties the real
// instance has, is still a foreign receiver.
function validateScheduler(self: unknown) {
if (self !== scheduler) {
throw new ERR_INVALID_THIS("Scheduler");
}
}
class Scheduler {
constructor() {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
// Not `async`: the `this` check has to reject a foreign receiver
// synchronously rather than returning a rejected promise.
wait(
delay: number,
options?: { signal?: AbortSignal },
): Promise<void> {
validateScheduler(this);
return setTimeoutPromise(delay, undefined, options);
}
yield() {
validateScheduler(this);
return promises.setImmediate();
}
}
const scheduler = ObjectCreate(Scheduler.prototype);
promises.scheduler = scheduler;View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Use the exported singleton: `import { scheduler } from 'node:timers'` and call `scheduler.wait(ms)` or `scheduler.yield()`
- For dependency injection or tests, wrap the singleton in your own function instead of subclassing Scheduler
- Keep importing Scheduler only for `instanceof` assertions
- Use timers/promises `setTimeout(ms)` when you only need a delayable promise
Example fix
// before
import { Scheduler } from "node:timers";
const s = new Scheduler(); // ERR_ILLEGAL_CONSTRUCTOR
await s.wait(100);
// after
import { scheduler } from "node:timers";
await scheduler.wait(100); Defensive patterns
Strategy: type-guard
Type guard
import { scheduler } from "node:timers";
const isScheduler = (v: unknown): v is typeof scheduler => v === scheduler; Try / catch
try {
await runLibrary(opts); // may construct Scheduler internally
} catch (e: any) {
if (e?.code === "ERR_ILLEGAL_CONSTRUCTOR") {
await scheduler.wait(opts.delay); // use the singleton instead
} else {
throw e;
}
} Prevention
- Never call new on the exported Scheduler class; it exists for instanceof checks only
- Route all waits through the imported scheduler singleton
- For testability, inject a wait function rather than constructing schedulers
- Borrowed scheduler methods also throw ERR_INVALID_THIS on foreign receivers
When it happens
Trigger: Executing `new Scheduler()` after importing it from node:timers; instantiating a subclass (`class S extends Scheduler {}` then `new S()`); calling `Scheduler.prototype.wait.call({}, 100)`; test utilities that enumerate module exports and try to construct every class.
Common situations: Porting scheduling code written against a constructible API; attempting per-request or per-test scheduler instances for isolation; mocking node:timers by subclassing Scheduler.
Related errors
- ERR_CONSTRUCT_CALL_REQUIRED
- ERR_CONSTRUCT_CALL_REQUIRED
- ERR_ILLEGAL_CONSTRUCTOR
- ERR_INVALID_THIS
- ERR_CONSTRUCT_CALL_REQUIRED
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/5043442593e2a8e0.
Report an issue: GitHub.