karatelabs/karate · error · io.karatelabs.js.EngineTimeoutException
timed out waiting for promise
Error message
timed out waiting for promise
What it means
join(timeout) waits on the promise future with a deadline; when it elapses, TimeoutException is translated into EngineTimeoutException("timed out waiting for promise"). It means the awaited promise did not settle within the allowed time — the JS producer never resolved or rejected.
Solutions
- Increase the timeout passed to join/await if the operation is legitimately slow.
- Audit the promise producer: ensure resolve/reject is always invoked on every code path.
- Add an upstream timeout to the slow operation (HTTP client timeout) so the promise rejects instead of hanging forever.
- Catch EngineTimeoutException and apply retry/fallback logic for transient slowness.
Example fix
// before
new Promise((resolve) => { /* resolve never called */ })
// after
new Promise((resolve, reject) => { setTimeout(() => resolve(val), 100); }) Defensive patterns
Strategy: retry
Validate before calling
// pre-check the awaited operation's own timeout is smaller than the join timeout:
if (timeout != null && upstreamTimeout > timeout.toMillis()) warn("join timeout shorter than upstream timeout"); Try / catch
try { return p.join(timeout); } catch (EngineTimeoutException e) { log.warn("promise did not settle within " + timeout); return fallback(); } Prevention
- Size the timeout to the slowest realistic upstream call plus margin.
- Put timeouts on the underlying operations (HTTP client, scheduler) so promises reject rather than hang.
- Audit Promise constructors for missing resolve/reject calls.
- Apply retry with backoff only for idempotent awaited operations.
When it happens
Trigger: await/join with a non-null timeout on a promise that stays pending: unresolved executor callback, a network call with no response, a generator that never resumes, or a timeout set smaller than the work required.
Common situations: Waiting on a fetch/backend call that hangs; forgetting to call resolve() in a Promise constructor; overly tight timeout configured in Karate steps wrapping async JS; deadlocked generator chain.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Timeout waiting for available driver for scenario
- <interrupted>
- Timeout waiting for Karate test events after
- Pool exhausted for scenario
- HTTP endpoint not available
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/cc20c70e0a151f07.
Report an issue: GitHub.
Appendix: source
Thrown at karate-js/src/main/java/io/karatelabs/js/JsPromise.java:242
* this from the engine thread would wait for a queue that nothing is
* pumping.
*/
public Object await() {
return join(null);
}
/** {@link #await()} with a deadline; throws {@link EngineTimeoutException}
* if the promise has not settled by then. */
public Object join(Duration timeout) {
try {
return timeout == null
? toFuture().get()
: toFuture().get(Math.max(timeout.toMillis(), 0), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new EngineInterruptedException();
} catch (TimeoutException e) {
throw new EngineTimeoutException("timed out waiting for promise");
} catch (ExecutionException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
if (cause instanceof RuntimeException re) {
throw re;
}
throw new EngineException(cause.getMessage(), cause);
}
}
/**
* The reverse association that makes a round trip lossless: handing a
* promise's own {@link #toFuture()} back into JS recovers the original
* {@code JsPromise}, independent of the scope's stage cache.
*/
static final class PromiseView extends CompletableFuture<Object> {
final JsPromise owner;
View on GitHub (pinned to a22eb90246)