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

  1. Await the promise inside try/catch: `try { await p } catch (e) { console.log(e.message) }`
  2. Or attach `.catch(err => ...)` to every fire-and-forget promise
  3. 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

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


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/d94db3bbb7b48992. Report an issue: GitHub.