grafana/k6 · error

setTimeout requires a function as first argument

Error message

setTimeout requires a function as first argument

What it means

k6/experimental exposes a timer API compatible in spirit with window.setTimeout, but implemented on the sobek runtime with a Go signature of (f sobek.Callable, t float64). If the first argument cannot be bound to a callable (or is nil after binding), the module throws immediately - unlike browsers, it does not accept a string of code to eval.

Solutions

  1. Pass an actual function reference: setTimeout(() => console.log('hi'), 100).
  2. Check the identifier exists: a typo'd function name evaluates to undefined at the call site and hits the nil-callable path.
  3. Do not port string-callback code from browsers; rewrite it as arrow functions.

Example fix

// before
setTimeout('console.log("done")', 1000); // throws

// after
setTimeout(() => console.log('done'), 1000);
Defensive patterns

Strategy: type-guard

Validate before calling

function safeSetTimeout(cb, ms, ...args) {
  if (typeof cb !== 'function') {
    throw new TypeError(`setTimeout callback must be a function, got ${typeof cb}`);
  }
  return setTimeout(cb, ms);
}

Type guard

const isFunction = (v) => typeof v === 'function';

Prevention

When it happens

Trigger: setTimeout('console.log(1)', 100) passing a string like the legacy browser API; setTimeout(123, 100); or calling setTimeout with no arguments so the callable parameter is nil.

Common situations: Copy-pasting browser setTimeout snippets that use string callbacks; renaming a callback that ends up undefined (typo) so the value passed is nil; assuming eval-style timers exist in k6.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of grafana/k6@01ffac6f24 (2026-08-18). Data as JSON: /api/errors/a2eed4dd28bf9175. Report an issue: GitHub.

Appendix: source

Thrown at internal/js/modules/k6/experimental/experimental.go:48

}

// New returns a new RootModule.
func New() *RootModule {
	return &RootModule{}
}

// Exports returns the exports of the experimental module
func (mi *ModuleInstance) Exports() modules.Exports {
	return modules.Exports{
		Named: map[string]any{
			"setTimeout": mi.setTimeout,
		},
	}
}

func (mi *ModuleInstance) setTimeout(f sobek.Callable, t float64) {
	if f == nil {
		common.Throw(mi.vu.Runtime(), errors.New("setTimeout requires a function as first argument"))
	}
	// TODO maybe really return something to use with `clearTimeout
	// TODO support arguments ... maybe
	runOnLoop := mi.vu.RegisterCallback()
	go func() {
		timer := time.NewTimer(time.Duration(t * float64(time.Millisecond)))
		select {
		case <-timer.C:
			runOnLoop(func() error {
				_, err := f(sobek.Undefined())
				return err
			})
		case <-mi.vu.Context().Done():
			// TODO log something?

			timer.Stop()
			runOnLoop(func() error { return nil })
		}

View on GitHub (pinned to 01ffac6f24)