{"record":{"id":"b038f1ef8ca2b98e","repo":"denoland/deno","slug":"illegal-invocation-b038f1","errorCode":null,"errorMessage":"Illegal invocation","messagePattern":"Illegal invocation","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/web/02_timers.js","lineNumber":44,"sourceCode":"  PromisePrototypeThen,\n  ReflectApply,\n  SafeMap,\n  TypeError,\n  indirectEval,\n} = primordials;\n\nconst webidl = core.loadExtScript(\"ext:deno_webidl/00_webidl.js\");\n\n// Map numeric timer IDs to internal core timer objects so clearTimeout /\n// clearInterval / refTimer / unrefTimer can look them up by id.\nconst activeTimers = new SafeMap();\n\n// WHATWG timer nesting depth tracking.\nlet timerDepth = 0;\n\nfunction checkThis(thisArg) {\n  if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) {\n    throw new TypeError(\"Illegal invocation\");\n  }\n}\n\n/**\n * Call a callback function after a delay.\n */\nfunction setTimeout(callback, timeout = 0, ...args) {\n  checkThis(this);\n  if (typeof callback !== \"function\") {\n    const unboundCallback = webidl.converters.DOMString(callback);\n    callback = () => indirectEval(unboundCallback);\n  }\n  const unboundCallback = callback;\n  const asyncContext = getAsyncContext();\n  const depth = timerDepth;\n  let id = 0;\n  const wrappedCallback = function () {\n    const oldContext = getAsyncContext();","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/web/02_timers.js#L26-L62","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nthis.wait = setTimeout;\nthis.wait(flush, 1000); // TypeError: Illegal invocation\n\n// after\nthis.wait = (fn, ms) => setTimeout(fn, ms);\nthis.wait(flush, 1000);","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"const safeTimer = (...args) => {\n  try {\n    return receiver.setTimeout(...args);\n  } catch (e) {\n    if (e instanceof TypeError && e.message === 'Illegal invocation') {\n      return setTimeout(...args);\n    }\n    throw e;\n  }\n};","preventionTips":["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"],"tags":["settimeout","timers","this-binding","web-api"],"backgroundTag":"illegal-invocation","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}