denoland/deno · error · TypeError
Illegal invocation
Error message
Illegal invocation
What it means
setTimeout/setInterval are bare global functions guarded by checkThis (ext/web/02_timers.js:41-45): calling them with a this other than null, undefined, or globalThis throws TypeError 'Illegal invocation'. The timers module keeps its id->timer map (activeTimers) at module scope and the web-exposed functions must run as globals. Typical trigger: storing setTimeout as an object property or class field and invoking it as a method, so this binds to the receiver.
Source
Thrown at ext/web/02_timers.js:44
PromisePrototypeThen,
ReflectApply,
SafeMap,
TypeError,
indirectEval,
} = primordials;
const webidl = core.loadExtScript("ext:deno_webidl/00_webidl.js");
// Map numeric timer IDs to internal core timer objects so clearTimeout /
// clearInterval / refTimer / unrefTimer can look them up by id.
const activeTimers = new SafeMap();
// WHATWG timer nesting depth tracking.
let timerDepth = 0;
function checkThis(thisArg) {
if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) {
throw new TypeError("Illegal invocation");
}
}
/**
* Call a callback function after a delay.
*/
function setTimeout(callback, timeout = 0, ...args) {
checkThis(this);
if (typeof callback !== "function") {
const unboundCallback = webidl.converters.DOMString(callback);
callback = () => indirectEval(unboundCallback);
}
const unboundCallback = callback;
const asyncContext = getAsyncContext();
const depth = timerDepth;
let id = 0;
const wrappedCallback = function () {
const oldContext = getAsyncContext();View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Call timers as bare globals: setTimeout(fn, 1000) or globalThis.setTimeout(fn, 1000).
- When capturing, wrap in an arrow ((...a) => setTimeout(...a)) or bind: const st = setTimeout.bind(globalThis).
- Search for .call(/.apply( around setTimeout/setInterval and for assignments like obj.wait = setTimeout.
Example fix
// before this.wait = setTimeout; this.wait(flush, 1000); // TypeError: Illegal invocation // after this.wait = (fn, ms) => setTimeout(fn, ms); this.wait(flush, 1000);
Defensive patterns
Strategy: fallback
Try / catch
const safeTimer = (...args) => {
try {
return receiver.setTimeout(...args);
} catch (e) {
if (e instanceof TypeError && e.message === 'Illegal invocation') {
return setTimeout(...args);
}
throw e;
}
}; Prevention
- Call timers as bare globals: setTimeout(fn, 1000)
- Bind when capturing: const st = setTimeout.bind(globalThis)
- Wrap in an arrow: (...a) => setTimeout(...a)
- Avoid storing global functions on objects that invoke them as methods
When it happens
Trigger: setTimeout.call({}, fn, 1000); const t = { set: setTimeout }; t.set(fn, 1000); this.timer = setTimeout; ... this.timer(fn, ms) inside a class — receiver is the instance, not globalThis.
Common situations: Classes capturing timers as fields for testability; DI/instrumentation wrappers that invoke captured functions with .call(receiver); bundlers or mocks re-hosting globals as object members. Node's timers tolerate arbitrary receivers, so code ported from Node can trip Deno's stricter web-shaped guard.
Related errors
- Illegal invocation
- Failed to construct 'Notification': 1 argument required, but
- Invalid header: length must be 2, but is ${header.length}
- Invalid header name: "${name}"
- Cannot change header: headers are immutable
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/b038f1ef8ca2b98e.
Report an issue: GitHub.