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

  1. Call combinators normally: `Promise.all(iterable)` or `MySubclass.all(iterable)` where MySubclass extends Promise.
  2. Don't rebind `this` with .call/.apply to a non-constructor value.
  3. 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

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


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)