karatelabs/karate · error · io.karatelabs.js.Abrupt

<JS rejection reason>

Error message

<JS rejection reason>

What it means

checkAbrupt converts a pending cooperative throw recorded on the Context into an Abrupt carrying the JS rejection reason, clearing the pending-error flag. Callers (asPromise, collect — e.g. Promise.all/allSettled/race/any iteration) use it so an in-flight script error surfaces as the promise's rejection reason instead of being silently dropped.

Solutions

  1. Find and fix the original JS throw — the Abrupt's payload is that reason; read its message/stack.
  2. Don't swallow errors inside iterator callbacks; let them reject the promise explicitly.
  3. Avoid continuing script execution after an uncaught sync error; check/reset context error state in host code.
  4. Wrap risky sections in try/catch in JS so errors become deliberate rejections.

Example fix

// before
arr.map(() => { throw new Error('boom'); }); // pending error surfaces later as Abrupt
// after
Promise.all(arr.map(() => Promise.reject(new Error('boom')))).catch(handle); // explicit rejection
Defensive patterns

Strategy: try-catch

Validate before calling

// in host code before running combinators: ensure no pending error
if (context instanceof CoreContext cc && cc.isError()) cc.reset(); // or handle it first

Try / catch

try { return Promise.all(...); } catch (Abrupt a) { handleRejection(a.value); /* a.value is the original JS rejection reason */ }

Prevention

When it happens

Trigger: During Promise combinator iteration (asPromise/collect), the context has isError() set — a prior JS throw is pending — so the pending error value is taken as the rejection reason and thrown as Abrupt.

Common situations: A callback passed to Promise.all's iterator throws mid-iteration; an earlier statement threw and the error is only observed at the next combinator boundary; user code mixing sync throws with async combinators.

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/7fc083834a6d1ae8. Report an issue: GitHub.

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/JsPromiseConstructor.java:165

     * itself completes normally with a rejected promise.
     */
    private static final class Abrupt extends RuntimeException {

        final transient Object reason;

        Abrupt(Object reason) {
            super(null, null, false, false);
            this.reason = reason;
        }
    }

    /** Converts a pending cooperative throw into an {@link Abrupt}, clearing the
     *  flag — from here on the error travels as the rejection reason. */
    private static void checkAbrupt(Context context) {
        if (context instanceof CoreContext cc && cc.isError()) {
            Object reason = cc.getErrorThrown();
            cc.reset();
            throw new Abrupt(reason);
        }
    }

    /** IteratorClose with the surrounding completion already abrupt: whatever
     *  {@code return()} does is discarded, the original reason wins. */
    private static void closeQuietly(JsIterator iter, Context context) {
        try {
            iter.close(context, true);
        } catch (RuntimeException e) {
            if (e instanceof FlowControlSignal || AsyncSupport.isHostCancellation(e)) {
                throw e;
            }
            // IteratorClose failing is swallowed per spec 7.4.6 step 4
        }
        if (context instanceof CoreContext cc && cc.isStopped()) {
            cc.reset(); // close() re-parks the completion it saw — we own the reason now
        }
    }

View on GitHub (pinned to a22eb90246)