karatelabs/karate · error · JsErrorException
Promise resolve did not return a thenable
Error message
Promise resolve did not return a thenable
What it means
Promise.all/allSettled/any resolve each element through the constructor's `resolve` and then read `then` off the result. If that custom (non-intrinsic) `resolve` returns something whose `then` is not callable, the engine cannot produce a promise wrapper and throws this TypeError instead of continuing.
Solutions
- Make the subclass's `resolve` return a thenable (a promise-like with callable `then`), or return `super.resolve(value)`.
- Don't override `Promise.resolve` unless you fully implement the thenable contract.
- Ensure `then` remains a callable property on resolved values.
Example fix
// before
class P extends Promise { static resolve(v) { return { value: v }; } }
// after
class P extends Promise { static resolve(v) { return Promise.resolve(v); } } Defensive patterns
Strategy: validation
Validate before calling
class SafeP extends Promise {
static resolve(v) {
const r = super.resolve(v);
if (!r || typeof r.then !== 'function') throw new TypeError('resolve must return a thenable');
return r;
}
} Type guard
function isThenable(v) { return v != null && typeof v.then === 'function'; } Try / catch
try { return SubPromise.all(items); } catch (e) { if (e instanceof TypeError && /did not return a thenable/.test(e.message)) return Promise.all(items); throw e; } Prevention
- Never override Promise.resolve without returning a thenable.
- Unit-test custom resolve() with thenable and non-thenable inputs.
- Keep `then` stable (callable) on objects fed to promise machinery.
When it happens
Trigger: Subclass overrides `Promise.resolve` (or the combinator is `.call`ed on a subclass) and the override returns a non-thenable value; a thenable's `then` was deleted or replaced with a non-function after the getter fired.
Common situations: Custom Promise subclasses with hand-rolled resolve(); monkey-patching Promise.resolve in test harnesses; exotic objects whose `then` accessor returns inconsistent values.
Related errors
- <JS rejection reason>
- Method Promise.prototype called on incompatible receiver
- Promise combinator called on a non-constructor
- Promise constructor cannot be invoked without 'new'
- Promise requires an active evaluation
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/13d4b22b9280a89a.
Report an issue: GitHub.
Appendix: source
Thrown at karate-js/src/main/java/io/karatelabs/js/JsPromiseConstructor.java:268
AtomicBoolean claimed = new AtomicBoolean();
JsCallable resolveFn = (ctx, a) -> {
if (claimed.compareAndSet(false, true)) {
AsyncSupport.settleFromCallback(wrapper, AsyncSupport.arg(a, 0), false);
}
return Terms.UNDEFINED;
};
JsCallable rejectFn = (ctx, a) -> {
if (claimed.compareAndSet(false, true)) {
AsyncSupport.settleFromCallback(wrapper, AsyncSupport.arg(a, 0), true);
}
return Terms.UNDEFINED;
};
callWithThis(context, thenCallable, value, new Object[]{resolveFn, rejectFn});
checkAbrupt(context);
return wrapper;
}
if (!intrinsic) {
throw JsErrorException.typeError("Promise resolve did not return a thenable");
}
if (value instanceof ObjectLike) {
// `then` was already read above — don't let resolveValue read it a
// second time (an accessor would fire twice)
wrapper.settle(false, value, null);
return wrapper;
}
AsyncSupport.resolveValue(wrapper, value, null);
return wrapper;
}
/**
* The shared combinator prologue — GetPromiseResolve, GetIterator, and the
* per-element {@code resolve} step (spec 27.2.4.1.1 / 27.2.4.1.2).
* <p>
* Every call into user code here can complete abruptly: the {@code resolve}
* lookup (an accessor), {@code resolve} itself, the {@code @@iterator}
* method, {@code next()}, and the {@code done} / {@code value} getters. OnView on GitHub (pinned to a22eb90246)