{"record":{"id":"d94db3bbb7b48992","repo":"grafana/k6","slug":"uncaught-in-promise-value","errorCode":null,"errorMessage":"Uncaught (in promise) ${value}","messagePattern":"Uncaught \\(in promise\\) (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/eventloop/eventloop.go","lineNumber":214,"sourceCode":"\t\t\t}\n\t\t\treturn newRejectionError(promise.Result(), value)\n\t\t}\n\t}\n}\n\nfunc newRejectionError(res, v sobek.Value) error {\n\tvar s string\n\tif v != nil {\n\t\ts = v.String()\n\t}\n\t// this is the de facto wording in both firefox and deno at least\n\tmsg := \"Uncaught (in promise) \" + s\n\tif !common.IsNullish(res) {\n\t\tif cause, ok := res.Export().(error); ok {\n\t\t\treturn &rejectionError{msg: msg, cause: cause}\n\t\t}\n\t}\n\treturn errors.New(msg)\n}\n\ntype rejectionError struct {\n\tmsg   string\n\tcause error\n}\n\nfunc (e *rejectionError) Error() string { return e.msg }\nfunc (e *rejectionError) Unwrap() error { return e.cause }\n\n// WaitOnRegistered waits on all registered callbacks so we know nothing is still doing work.\n// This does call back the callbacks and more can be queued over time.\n// A different mechanism needs to be used to tell the users that the event loop has errored out or winding down for a\n// different reason.\nfunc (e *EventLoop) WaitOnRegistered() {\n\tfor {\n\t\tqueue, awaiting := e.popAll()\n\t\tif len(queue) == 0 {","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/eventloop/eventloop.go#L196-L232","documentation":"The k6 event loop tracks Promises that rejected with no handler attached (pendingPromiseRejections). When queued work drains and such a Promise exists, the loop returns 'Uncaught (in promise) <value>', mirroring Firefox/Deno wording; the rejection value (or its stack) is embedded and the original error is wrapped as cause when exportable.","triggerScenarios":"Any async API (http.asyncRequest, browser promises, async user code, timers that throw) whose rejection has no .catch and is not awaited in a try/catch by the time the event loop finishes the iteration/setup phase.","commonSituations":"Missing `await` inside async default/setup functions; fire-and-forget http.asyncRequest to an unreachable host; .then chains whose callback throws.","solutions":["Await the promise inside try/catch: `try { await p } catch (e) { console.log(e.message) }`","Or attach `.catch(err => ...)` to every fire-and-forget promise","Read the embedded value/stack in the message to locate the unhandled rejection source"],"exampleFix":"// before\nimport http from 'k6/http';\nexport default async function () {\n  http.asyncRequest('GET', 'https://host/'); // rejection unhandled\n}\n\n// after\nexport default async function () {\n  try { await http.asyncRequest('GET', 'https://host/'); }\n  catch (e) { console.log('request failed:', e.message); }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Wrap every await that can reject; catch fire-and-forget promises\nexport default async function () {\n  try {\n    const res = await http.asyncRequest('GET', URL);\n    check(res, { ok: (r) => r.status === 200 });\n  } catch (e) {\n    console.error(`request failed: ${e.message}`); // rejection handled, iteration continues\n  }\n}\n// fire-and-forget variant: somePromise.catch((e) => console.error(e.message));","preventionTips":["Never call an async k6 API without either `await`+try/catch or an attached .catch","Enable `--verbose` locally to see which promise rejected when the message is vague","Treat 'Uncaught (in promise)' as a script bug, not a target-system failure"],"tags":["javascript","promises","async","eventloop"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}