karatelabs/karate · error · JsErrorException

Method Promise.prototype called on incompatible receiver

Error message

Method Promise.prototype called on incompatible receiver

What it means

Every method on Promise.prototype funnels through `asPromise`, which narrows `this` to an actual JsPromise instance. If a method like `then`, `catch`, or `finally` is invoked with a `this` that is not an internal promise object (plain object, undefined, or a foreign thenable), the engine throws this TypeError — the standard 'incompatible receiver' guard.

Solutions

  1. Only call promise methods on real promise instances: `promise.then(...)`.
  2. If borrowing, wrap: `(p) => Promise.prototype.then.call(realPromise, ...)` — but ensure the receiver is genuinely a JsPromise.
  3. Bind detached references: keep `then` attached to the instance instead of extracting it.

Example fix

// before
const then = Promise.prototype.then;
then(result);
// after
result.then((v) => ...);
Defensive patterns

Strategy: type-guard

Validate before calling

function safeThen(p, onF, onR) {
  if (p == null || typeof p.then !== 'function') throw new TypeError('receiver is not a promise');
  return p.then(onF, onR);
}

Type guard

function isPromiseLike(v) { return v != null && typeof v.then === 'function'; }

Try / catch

try { return value.then(cb); } catch (e) { if (e instanceof TypeError && /incompatible receiver/.test(e.message)) return Promise.resolve(value).then(cb); throw e; }

Prevention

When it happens

Trigger: `const t = Promise.prototype.then; t(x)` with undefined this; `myObj.then = Promise.prototype.then; myObj.then(...)` on a non-promise object; destructuring then off a Promise instance and calling it detached.

Common situations: Borrowing Promise.prototype methods onto plain objects; passing prototype methods as callbacks without binding; JS transpilation artifacts that lose `this`.

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/1554eba8ae070b99. Report an issue: GitHub.

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/JsPromisePrototype.java:62

    }

    /**
     * True while {@code value} is still the untampered {@code Promise.prototype.then}.
     * The combinators' fast path (register a reaction directly, skipping the
     * user-visible {@code Invoke(p, "then", …)}) is only valid while it holds —
     * an own {@code then} on the element, or a replaced prototype method, is
     * observable and must actually be called.
     */
    static boolean isIntrinsicThen(Object value) {
        return value instanceof JsBuiltinMethod method && method.delegate() == INSTANCE.thenDelegate;
    }

    private static JsPromise asPromise(Context context) {
        Object thisObj = context.getThisObject();
        if (thisObj instanceof JsPromise promise) {
            return promise;
        }
        throw JsErrorException.typeError("Method Promise.prototype called on incompatible receiver");
    }

    private static JsCallable callableOrNull(Object value) {
        return value instanceof JsCallable callable ? callable : null;
    }

    private Object then(Context context, Object[] args) {
        JsPromise self = asPromise(context);
        return then(self, callableOrNull(AsyncSupport.arg(args, 0)), callableOrNull(AsyncSupport.arg(args, 1)));
    }

    static JsPromise then(JsPromise self, JsCallable onFulfilled, JsCallable onRejected) {
        JsPromise derived = new JsPromise(self.engine, self.scope);
        self.addReaction((rejected, value) -> {
            JsCallable handler = rejected ? onRejected : onFulfilled;
            if (handler == null) {
                // no handler for this outcome: pass it straight to the derived
                // promise, which is now the one carrying the responsibility

View on GitHub (pinned to a22eb90246)