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
- Run the call inside an Engine evaluation (e.g. Engine.evalRaw / within the context that produced the function).
- Ensure the JsFunctionNode's CoreContext was created by and still references a live Engine.
- 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
- Only execute JS functions through Engine evaluation entry points.
- Never retain and reuse JsFunctionNode/CoreContext after an evaluation completes.
- Route background work through the engine's async scheduling instead of direct calls.
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
- setTimeout: karate is shutting down
- setTimeout:
- engine is poisoned:
- 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/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)