karatelabs/karate · error · JsErrorException

async function called outside an engine evaluation

Error message

async function called outside an engine evaluation

What it means

Karate's JS engine throws this when an async JS function is invoked but no Engine is associated with the current evaluation context. Async invocations need an engine to resolve the current AsyncScope (or fall back to a host async call); without one the result promise could never be managed, so the library fails fast with a TypeError instead of silently misbehaving.

Solutions

  1. Run the call inside an Engine evaluation (e.g. Engine.evalRaw / within the context that produced the function).
  2. Ensure the JsFunctionNode's CoreContext was created by and still references a live Engine.
  3. Do not call bound JS functions from arbitrary worker threads — schedule work through the engine's async machinery instead.

Example fix

// before: function called from plain Java thread
Object result = AsyncSupport.callAsync(fn, bareContext, args);
// after: evaluate inside an engine context
Object result = engine.evalRaw("myAsyncFn()", args);
Defensive patterns

Strategy: type-guard

Validate before calling

if (context.getEngine() == null) throw new IllegalStateException("call async functions inside an Engine evaluation");

Type guard

function hasEngine(ctx) { return ctx != null && ctx.getEngine() != null; }

Try / catch

try { return AsyncSupport.callAsync(fn, ctx, args); } catch (JsErrorException e) { if (e.getMessage().contains("outside an engine evaluation")) { /* re-run inside engine */ } else throw e; }

Prevention

When it happens

Trigger: Calling a JS `async function` via AsyncSupport.callAsync when functionContext.getEngine() returns null — e.g. invoking the function outside of Engine.evalRaw/eval, or holding a JsFunctionNode from one engine and executing it after that evaluation has ended.

Common situations: Caching a JS function and calling it later from a plain Java thread; invoking a Karate JS function from a custom step or framework hook that bypasses the engine; calling async functions during engine teardown.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/9ae9b9383c3020a2. Report an issue: GitHub.

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/AsyncSupport.java:188

        } catch (RuntimeException e) {
            if (e instanceof FlowControlSignal || isHostCancellation(e)) {
                throw e;
            }
            if (e instanceof AwaitRejection ar) {
                return new Completion(null, ar.reason, true);
            }
            return new Completion(null, reasonOf(e), true);
        }
    }

    //=== activations ==================================================================================================

    /** Entry point from {@link JsFunctionNode#bindArgsAndExecute}: an async
     *  invocation returns its promise, never its body's value. */
    static Object callAsync(JsFunctionNode function, CoreContext functionContext, Object[] args) {
        Engine engine = functionContext.getEngine();
        if (engine == null) {
            throw JsErrorException.typeError("async function called outside an engine evaluation");
        }
        AsyncScope scope = engine.currentScope();
        if (scope != null) {
            return AsyncActivation.spawn(engine, scope, function, functionContext, args);
        }
        return hostAsyncCall(engine, function, functionContext, args);
    }

    /**
     * A Java caller invoking an async JS function directly, outside any eval:
     * open a scope for the call, spawn, drain to quiescence, and hand back the
     * promise (never an auto-awaited value — the host decides).
     */
    private static Object hostAsyncCall(Engine engine, JsFunctionNode function,
                                        CoreContext functionContext, Object[] args) {
        engine.checkPoisoned();
        boolean outermost = engine.enterEvalScope();
        AsyncScope scope = engine.currentScope();

View on GitHub (pinned to a22eb90246)