{"record":{"id":"a2eed4dd28bf9175","repo":"grafana/k6","slug":"settimeout-requires-a-function-as-first-argument","errorCode":null,"errorMessage":"setTimeout requires a function as first argument","messagePattern":"setTimeout requires a function as first argument","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/experimental/experimental.go","lineNumber":48,"sourceCode":"}\n\n// New returns a new RootModule.\nfunc New() *RootModule {\n\treturn &RootModule{}\n}\n\n// Exports returns the exports of the experimental module\nfunc (mi *ModuleInstance) Exports() modules.Exports {\n\treturn modules.Exports{\n\t\tNamed: map[string]any{\n\t\t\t\"setTimeout\": mi.setTimeout,\n\t\t},\n\t}\n}\n\nfunc (mi *ModuleInstance) setTimeout(f sobek.Callable, t float64) {\n\tif f == nil {\n\t\tcommon.Throw(mi.vu.Runtime(), errors.New(\"setTimeout requires a function as first argument\"))\n\t}\n\t// TODO maybe really return something to use with `clearTimeout\n\t// TODO support arguments ... maybe\n\trunOnLoop := mi.vu.RegisterCallback()\n\tgo func() {\n\t\ttimer := time.NewTimer(time.Duration(t * float64(time.Millisecond)))\n\t\tselect {\n\t\tcase <-timer.C:\n\t\t\trunOnLoop(func() error {\n\t\t\t\t_, err := f(sobek.Undefined())\n\t\t\t\treturn err\n\t\t\t})\n\t\tcase <-mi.vu.Context().Done():\n\t\t\t// TODO log something?\n\n\t\t\ttimer.Stop()\n\t\t\trunOnLoop(func() error { return nil })\n\t\t}","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/grafana/k6/blob/01ffac6f245854c1b8adc6a69857c76a15f90022/internal/js/modules/k6/experimental/experimental.go#L30-L66","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass an actual function reference: setTimeout(() => console.log('hi'), 100).","Check the identifier exists: a typo'd function name evaluates to undefined at the call site and hits the nil-callable path.","Do not port string-callback code from browsers; rewrite it as arrow functions."],"exampleFix":"// before\nsetTimeout('console.log(\"done\")', 1000); // throws\n\n// after\nsetTimeout(() => console.log('done'), 1000);","handlingStrategy":"type-guard","validationCode":"function safeSetTimeout(cb, ms, ...args) {\n  if (typeof cb !== 'function') {\n    throw new TypeError(`setTimeout callback must be a function, got ${typeof cb}`);\n  }\n  return setTimeout(cb, ms);\n}","typeGuard":"const isFunction = (v) => typeof v === 'function';","tryCatchPattern":null,"preventionTips":["Never port string-form setTimeout from browser code.","Lint for setTimeout('...') string literals in k6 projects."],"tags":["k6","settimeout","timers","type-error","experimental"],"backgroundTag":"invalid-argument-value","analyzedSha":"01ffac6f245854c1b8adc6a69857c76a15f90022","analyzedAt":"2026-08-18T03:05:52.393Z","contentChangedAt":"2026-08-18T03:05:52.393Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}