karatelabs/karate · error · JsErrorException
setTimeout: callback is not a function
Error message
setTimeout: callback is not a function
What it means
setTimeout's first argument must be a JsCallable (a function). Any other value — string, number, object, null — is rejected with a TypeError, mirroring the JS spec's requirement that the callback be callable.
Solutions
- Pass an actual function reference: setTimeout(() => ..., delay).
- Check the value with typeof cb === 'function' before scheduling.
- If migrating string-based setTimeout, wrap the code in an arrow function.
Example fix
// before
setTimeout('doWork()', 100);
// after
setTimeout(() => doWork(), 100); Defensive patterns
Strategy: type-guard
Validate before calling
if (typeof callback !== 'function') throw new TypeError('setTimeout callback must be a function'); Type guard
function isCallable(v) { return typeof v === 'function'; } Try / catch
try { setTimeout(cb, 100); } catch (e) { if (e instanceof TypeError && e.message.includes('not a function')) { /* inspect cb value */ } else throw e; } Prevention
- Always pass function expressions, not strings.
- Guard optional callbacks: if (typeof maybeCb === 'function') setTimeout(maybeCb, d).
- Avoid porting browser string-callback snippets verbatim.
When it happens
Trigger: setTimeout('someCode', 100) or setTimeout(undefined) / setTimeout(null) / setTimeout(obj.method) where the property is missing — i.e. arg(args,0) does not implement JsCallable.
Common situations: Porting browser snippets that pass a string of code (browser-only behavior); passing the result of an optional lookup that came back undefined; a typo making the callback resolve to a non-function.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Dynamic expression must return a list or function
- set() with a single argument expects a Map / JSON object
- prettyXml() argument must be XML node or string
- setTimeout: no engine is executing on this thread
- setTimeout requires an active evaluation
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/19e3cfcc147b368d.
Report an issue: GitHub.
Appendix: source
Thrown at karate-js/src/main/java/io/karatelabs/js/AsyncSupport.java:742
* thread's current engine — scheduling without a definite engine is an
* error, not a guess. */
private static Engine timerEngine(Context context) {
Engine engine = context == null ? null : context.getEngine();
if (engine == null || engine != Engine.current()) {
throw JsErrorException.typeError("setTimeout: no engine is executing on this thread");
}
return engine;
}
static Object setTimeout(Context context, Object[] args) {
Engine engine = timerEngine(context);
AsyncScope scope = engine.currentScope();
if (scope == null) {
throw JsErrorException.typeError("setTimeout requires an active evaluation");
}
Object callback = arg(args, 0);
if (!(callback instanceof JsCallable target)) {
throw JsErrorException.typeError("setTimeout: callback is not a function");
}
long delay = coerceDelay(arg(args, 1));
Object[] extra = args.length > 2 ? Arrays.copyOfRange(args, 2, args.length) : EMPTY_ARGS;
if (KarateLifecycle.phase() != KarateLifecycle.Phase.RUNNING) {
// refuse before a token is acquired — scheduling is about to be
// rejected anyway, and an unbacked token would strand the drain
throw new EngineInterruptedException("setTimeout: karate is shutting down");
}
AsyncToken token = scope.acquire("timer");
if (token == null) {
throw new EngineInterruptedException();
}
AsyncActivation.anyAsync = true;
int id = scope.nextTimerId();
AsyncScope.TimerRecord record = new AsyncScope.TimerRecord(scope, id, token);
scope.addTimer(record);
TimerScheduler timer = scheduler();
// the closed-check and the registration are one atomic step under theView on GitHub (pinned to a22eb90246)