karatelabs/karate · error · JsErrorException
Promise combinator called on a non-constructor
Error message
Promise combinator called on a non-constructor
What it means
`Promise.all/race/allSettled/any` are generic: they use their `this` value as the constructor for building the result. The engine's `constructorOf` accepts either the intrinsic Promise or an ObjectLike that is a constructable callable (a subclass); anything else — e.g. `Promise.all.call(plainObj, ...)` or a `this` that lost constructability — throws this TypeError.
Solutions
- Call combinators normally: `Promise.all(iterable)` or `MySubclass.all(iterable)` where MySubclass extends Promise.
- Don't rebind `this` with .call/.apply to a non-constructor value.
- If using a subclass, ensure it is actually constructable (`new MySubclass(...)` works).
Example fix
// before
const out = Promise.all.call({}, promises);
// after
const out = Promise.all(promises); Defensive patterns
Strategy: type-guard
Validate before calling
function safeAll(receiversCtor, iterable) {
const ctor = receiversCtor || Promise;
if (typeof ctor !== 'function' || !ctor.prototype) throw new TypeError('combinator receiver must be a constructor');
return Promise.all.call(ctor, iterable);
} Type guard
function isConstructable(f) { return typeof f === 'function' && !!f.prototype; } Try / catch
try { return Promise.all(iterable); } catch (e) { if (e instanceof TypeError && /non-constructor/.test(e.message)) return Promise.all.call(Promise, iterable); throw e; } Prevention
- Avoid .call/.apply on Promise combinators unless subclassing intentionally.
- Keep combinator receivers to Promise or subclasses of Promise.
- Test custom subclasses with `new Sub(...)` before using them as combinator receivers.
When it happens
Trigger: `Promise.all.call(nonConstructor, iterable)`; invoking a combinator with `this` rebound to a plain object or non-constructable function; subclass patterns where the subclass fails `isConstructable()`.
Common situations: Borrowing Promise.all via .call/.apply from another object; broken subclassing (class without proper constructor semantics in the transpiled/host-bridged code).
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
- Method Promise.prototype called on incompatible receiver
- <JS rejection reason>
- BigInt.prototype method called on non-BigInt
- this is not a Date object
- Error.prototype.toString called on non-object
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/df772cfa5460c2e1.
Report an issue: GitHub.
Appendix: source
Thrown at karate-js/src/main/java/io/karatelabs/js/JsPromiseConstructor.java:219
/**
* The combinator's {@code this} value, i.e. spec {@code C}. Honored so
* {@code Promise.all.call(C, …)} consults C's own {@code resolve}; an absent
* receiver (a Java host call) falls back to the intrinsic. The result promise
* is still always a native one — NewPromiseCapability over a subclass
* constructor is not modelled — but IsConstructor(C) is enforced, since that
* is the one part of the capability step observable from here.
*/
private ObjectLike constructorOf(Context context) {
Object thisValue = context.getThisObject();
if (thisValue == null || thisValue == Terms.UNDEFINED || thisValue == this) {
return this;
}
if (thisValue instanceof ObjectLike obj
&& thisValue instanceof JsCallable callable && callable.isConstructable()) {
return obj;
}
throw JsErrorException.typeError("Promise combinator called on a non-constructor");
}
/**
* The spec's {@code Invoke(nextPromise, "then", «resolveElement, reject»)}.
* Fast path: the element is a native promise still resolving the untampered
* {@code Promise.prototype.then}, so nothing observable happens and the
* reaction can be registered on it directly. Otherwise {@code then} really is
* user code — read it and call it here, so a throwing getter or a throwing
* {@code then} aborts the combinator loop rather than being absorbed into
* the element promise (which, with an endless iterator, never terminates).
* <p>
* {@code intrinsic} says the value came from the untampered
* {@code Promise.resolve}, which always hands back a native promise. When it
* did not, the spec's {@code Invoke} is the whole story: whatever a custom
* {@code C.resolve} returned must have a callable {@code then}, and a
* primitive — or an object without one — is a {@code TypeError} that rejects
* the combinator, not something to wrap and fulfil with.
*/View on GitHub (pinned to a22eb90246)