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

  1. Pass an actual function reference: setTimeout(() => ..., delay).
  2. Check the value with typeof cb === 'function' before scheduling.
  3. 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

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.

Related errors


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 the

View on GitHub (pinned to a22eb90246)