{"record":{"id":"2636bbb101e757e1","repo":"karatelabs/karate","slug":"await-on-a-promise-that-can-never-settle","errorCode":null,"errorMessage":"await on a promise that can never settle","messagePattern":"await on a promise that can never settle","errorType":"exception","errorClass":"EngineException","httpStatus":null,"severity":"error","filePath":"karate-js/src/main/java/io/karatelabs/js/AsyncSupport.java","lineNumber":377,"sourceCode":"        // On an abrupt exit the lock is deliberately NOT reacquired: a running\n        // activation may hold it, and this thread is on its way to tearing the\n        // scope down. Every enclosing frame unlocks conditionally for that reason.\n        while (!promise.isSettled()) {\n            if (Thread.currentThread().isInterrupted() || scope.isCancelRequested()) {\n                throw new EngineInterruptedException();\n            }\n            if (deadlineNanos != 0 && System.nanoTime() > deadlineNanos) {\n                throw new EngineTimeoutException(\"async drain timed out\");\n            }\n            AsyncJob job = scope.takeJob(POLL_MILLIS);\n            if (job instanceof AsyncJob.Control control) {\n                job.token.release();\n                throw new EngineInterruptedException(control.reason);\n            }\n            if (job != null) {\n                runJob(engine, job);\n            } else if (scope.isQuiescent() && !promise.isSettled()) {\n                throw new EngineException(\"await on a promise that can never settle\", null);\n            }\n        }\n        engine.reacquireJsLock(holds);\n        return settledResult(promise);\n    }\n\n    //=== adoption =====================================================================================================\n\n    /**\n     * The one adoption operation, shared by the {@code Promise} constructor's\n     * resolve, {@code Promise.resolve}, async-function return, {@code .then}\n     * results and {@code CompletionStage} wrapping. JS-level rules run before\n     * any CF composition.\n     */\n    static void resolveValue(JsPromise target, Object value, AsyncToken retiring) {\n        if (value == target) {\n            target.settle(true, JsErrorException.typeError(\"Chaining cycle detected for promise\").payload, retiring);\n            return;","sourceCodeStart":359,"sourceCodeEnd":395,"githubUrl":"https://github.com/karatelabs/karate/blob/a22eb90246d958d15a47bf436693d0121ad2812d/karate-js/src/main/java/io/karatelabs/js/AsyncSupport.java#L359-L395","documentation":"While pumping jobs waiting for a promise, if the scope becomes quiescent (no pending jobs, nothing to run) but the promise is still unsettled, AsyncSupport throws EngineException 'await on a promise that can never settle' — no remaining work could ever resolve it, so waiting longer is pointless.","triggerScenarios":"Calling await(promise) where the promise was created but its resolving job already ran without settling it, resolution depends on a callback that was never registered, or the promise's producer was dropped during scope teardown.","commonSituations":"Promise constructed with a resolve captured in a closure that is never invoked; awaiting a promise after the engine/scope has finished its jobs (e.g. post-teardown code); races where the settle happens on a thread not owned by the scope.","solutions":["Ensure every promise has a code path that calls resolve/reject regardless of control flow","Verify you await the correct promise instance and that its producer was actually scheduled before awaiting","Reproduce with logging in the promise executor to confirm whether the executor body ever runs","Catch EngineException, inspect the promise creation site, and restructure so settlement is owned by scheduled scope jobs"],"exampleFix":"// before (JS)\nlet p = new Promise(() => {});      // executor never settles\nawait p;\n// after (JS)\nlet p = new Promise((resolve) => setTimeout(() => resolve(42), 10));\nawait p; // always settles","handlingStrategy":"try-catch","validationCode":"// JS: wrap promise creation so accidental never-settling executors throw in dev\nlet p = new Promise((resolve, reject) => {\n    setTimeout(() => reject(new Error('producer did not settle')), 30_000); // watchdog\n});","typeGuard":null,"tryCatchPattern":"try {\n    Object result = engine.await(promise);\n} catch (EngineException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"never settle\")) {\n        throw new ScriptBugException(\"promise producer never settles — fix the producer, not the await\");\n    }\n    throw e;\n}","preventionTips":["Never leave a promise executor without calling resolve or reject","Only await promises whose producers were scheduled in the same live scope","Add a watchdog timeout inside long-lived promises during development","After scope teardown, do not create or await new promises"],"tags":["async","promise","javascript-engine"],"backgroundTag":"promise-never-settles","analyzedSha":"a22eb90246d958d15a47bf436693d0121ad2812d","analyzedAt":"2026-09-12T09:01:00.220Z","contentChangedAt":"2026-09-12T09:01:00.220Z","schemaVersion":2},"datasetVersion":"2026-09-16T19:17:19.609Z"}