{"record":{"id":"9d01a2575f618808","repo":"grafana/k6","slug":"uncaught-in-promise-reason","errorCode":null,"errorMessage":"Uncaught (in promise) ${reason}","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/01ffac6f245854c1b8adc6a69857c76a15f90022/internal/js/eventloop/eventloop.go#L196-L232","documentation":"k6's JS event loop (internal/js/eventloop/eventloop.go:214, newRejectionError) mirrors browser behavior: a promise that rejects with no attached handler is converted into an 'Uncaught (in promise) <reason>' error, failing the iteration/test. The rejectionError wrapper unwraps to the underlying cause error when available.","triggerScenarios":"Any promise that rejects unhandled: `Promise.reject(...)`, a `.then()` chain without `.catch`, an `async` function invoked without awaiting/catching that throws, unhandled rejections from k6/browser or other async APIs during iteration.","commonSituations":"Fire-and-forget async calls in VU code; refactoring synchronous calls to async without adding await; error thrown inside setTimeout/setInterval-style callbacks; third-party JS bundles (via webpack) that reject promises internally.","solutions":["Add `.catch(err => ...)` to every promise chain.","In async functions, `await` promises inside try/catch so rejections surface where you handle them.","Attach a global handler pattern (e.g. wrapping your default function body) so no rejection escapes unobserved."],"exampleFix":"// before\nhttp.async ? null : null;\nPromise.reject(new Error('boom')); // unhandled\n// after\nPromise.reject(new Error('boom')).catch(e => console.error('handled:', e));","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// pattern: never let a promise reject unobserved in a VU\nexport default async function () {\n  try {\n    await doAsyncWork();\n  } catch (err) {\n    console.error(`iteration failed: ${err}`); // handle or rethrow deliberately\n    throw err;\n  }\n}\n// for fire-and-forget chains, attach:\nsomePromise().catch(err => console.error('background failure:', err));","preventionTips":["Await every async call inside try/catch; never call async functions without await or .catch.","Add a lint rule (no-floating-promises) when scripts are bundled from TypeScript.","Remember the error's cause is unwrappable — inspect err.cause for the real rejection reason."],"tags":["javascript","async","promises","eventloop"],"backgroundTag":"unhandled-promise-rejection","analyzedSha":"01ffac6f245854c1b8adc6a69857c76a15f90022","analyzedAt":"2026-08-18T03:05:52.393Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}