{"record":{"id":"13d4b22b9280a89a","repo":"karatelabs/karate","slug":"promise-resolve-did-not-return-a-thenable","errorCode":null,"errorMessage":"Promise resolve did not return a thenable","messagePattern":"Promise resolve did not return a thenable","errorType":"exception","errorClass":"JsErrorException","httpStatus":null,"severity":"error","filePath":"karate-js/src/main/java/io/karatelabs/js/JsPromiseConstructor.java","lineNumber":268,"sourceCode":"            AtomicBoolean claimed = new AtomicBoolean();\n            JsCallable resolveFn = (ctx, a) -> {\n                if (claimed.compareAndSet(false, true)) {\n                    AsyncSupport.settleFromCallback(wrapper, AsyncSupport.arg(a, 0), false);\n                }\n                return Terms.UNDEFINED;\n            };\n            JsCallable rejectFn = (ctx, a) -> {\n                if (claimed.compareAndSet(false, true)) {\n                    AsyncSupport.settleFromCallback(wrapper, AsyncSupport.arg(a, 0), true);\n                }\n                return Terms.UNDEFINED;\n            };\n            callWithThis(context, thenCallable, value, new Object[]{resolveFn, rejectFn});\n            checkAbrupt(context);\n            return wrapper;\n        }\n        if (!intrinsic) {\n            throw JsErrorException.typeError(\"Promise resolve did not return a thenable\");\n        }\n        if (value instanceof ObjectLike) {\n            // `then` was already read above — don't let resolveValue read it a\n            // second time (an accessor would fire twice)\n            wrapper.settle(false, value, null);\n            return wrapper;\n        }\n        AsyncSupport.resolveValue(wrapper, value, null);\n        return wrapper;\n    }\n\n    /**\n     * The shared combinator prologue — GetPromiseResolve, GetIterator, and the\n     * per-element {@code resolve} step (spec 27.2.4.1.1 / 27.2.4.1.2).\n     * <p>\n     * Every call into user code here can complete abruptly: the {@code resolve}\n     * lookup (an accessor), {@code resolve} itself, the {@code @@iterator}\n     * method, {@code next()}, and the {@code done} / {@code value} getters. On","sourceCodeStart":250,"sourceCodeEnd":286,"githubUrl":"https://github.com/karatelabs/karate/blob/a22eb90246d958d15a47bf436693d0121ad2812d/karate-js/src/main/java/io/karatelabs/js/JsPromiseConstructor.java#L250-L286","documentation":"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.","triggerScenarios":"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.","commonSituations":"Custom Promise subclasses with hand-rolled resolve(); monkey-patching Promise.resolve in test harnesses; exotic objects whose `then` accessor returns inconsistent values.","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."],"exampleFix":"// before\nclass P extends Promise { static resolve(v) { return { value: v }; } }\n// after\nclass P extends Promise { static resolve(v) { return Promise.resolve(v); } }","handlingStrategy":"validation","validationCode":"class SafeP extends Promise {\n  static resolve(v) {\n    const r = super.resolve(v);\n    if (!r || typeof r.then !== 'function') throw new TypeError('resolve must return a thenable');\n    return r;\n  }\n}","typeGuard":"function isThenable(v) { return v != null && typeof v.then === 'function'; }","tryCatchPattern":"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; }","preventionTips":["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."],"tags":["javascript","promise","thenable","subclassing"],"backgroundTag":"invalid-thenable-resolution","analyzedSha":"a22eb90246d958d15a47bf436693d0121ad2812d","analyzedAt":"2026-09-12T09:01:00.220Z","contentChangedAt":"2026-09-12T09:01:00.220Z","schemaVersion":2},"datasetVersion":"2026-09-19T12:17:13.211Z"}