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

  1. Make the subclass's `resolve` return a thenable (a promise-like with callable `then`), or return `super.resolve(value)`.
  2. Don't override `Promise.resolve` unless you fully implement the thenable contract.
  3. 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

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


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. On

View on GitHub (pinned to a22eb90246)