grafana/k6 · error
Uncaught (in promise) ${value}
Error message
Uncaught (in promise) ${value} What it means
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.
Source
Thrown at internal/js/eventloop/eventloop.go:214
}
return newRejectionError(promise.Result(), value)
}
}
}
func newRejectionError(res, v sobek.Value) error {
var s string
if v != nil {
s = v.String()
}
// this is the de facto wording in both firefox and deno at least
msg := "Uncaught (in promise) " + s
if !common.IsNullish(res) {
if cause, ok := res.Export().(error); ok {
return &rejectionError{msg: msg, cause: cause}
}
}
return errors.New(msg)
}
type rejectionError struct {
msg string
cause error
}
func (e *rejectionError) Error() string { return e.msg }
func (e *rejectionError) Unwrap() error { return e.cause }
// WaitOnRegistered waits on all registered callbacks so we know nothing is still doing work.
// This does call back the callbacks and more can be queued over time.
// A different mechanism needs to be used to tell the users that the event loop has errored out or winding down for a
// different reason.
func (e *EventLoop) WaitOnRegistered() {
for {
queue, awaiting := e.popAll()
if len(queue) == 0 {View on GitHub (pinned to 93accf6570)
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
Example fix
// before
import http from 'k6/http';
export default async function () {
http.asyncRequest('GET', 'https://host/'); // rejection unhandled
}
// after
export default async function () {
try { await http.asyncRequest('GET', 'https://host/'); }
catch (e) { console.log('request failed:', e.message); }
} Defensive patterns
Strategy: try-catch
Try / catch
// Wrap every await that can reject; catch fire-and-forget promises
export default async function () {
try {
const res = await http.asyncRequest('GET', URL);
check(res, { ok: (r) => r.status === 200 });
} catch (e) {
console.error(`request failed: ${e.message}`); // rejection handled, iteration continues
}
}
// fire-and-forget variant: somePromise.catch((e) => console.error(e.message)); Prevention
- 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
When it happens
Trigger: 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.
Common situations: Missing `await` inside async default/setup functions; fire-and-forget http.asyncRequest to an unreachable host; .then chains whose callback throws.
Related errors
- group() does not support async functions as arguments, pleas
- the built-in check() does not support async functions as arg
- Unexpected end of selector while parsing selector `${selecto
- Error while parsing selector `${selector}`: ${e.message}
- Error while parsing selector `${selector}` - cannot use ${op
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/d94db3bbb7b48992.
Report an issue: GitHub.