{"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/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/experimental/experimental.go#L30-L66","documentation":"k6/experimental's setTimeout only supports a callable as its first argument; sobek binds the parameter as sobek.Callable, and any non-function value (including the browser-style string-eval form) binds as nil, which this guard rejects. Argument passing and clearTimeout are not supported yet (marked TODO in the source).","triggerScenarios":"setTimeout('console.log(1)', 100) using the browser string form; passing undefined or a non-function variable; passing the result of calling the function -- setTimeout(fn(), 100) -- instead of the reference setTimeout(fn, 100).","commonSituations":"Copy-pasting browser or Node.js timer code into a k6 script; passing extra arguments expecting them to be forwarded to the callback (not supported); timers configured from external JSON config strings.","solutions":["Pass a function reference: setTimeout(fn, 100)","For arguments, wrap in a closure: setTimeout(() => fn(arg1, arg2), 100)","If the delay value comes from config, ensure the function slot is a real function, not a serialized string"],"exampleFix":"// before\nsetTimeout(doWork('x'), 100);\n\n// after\nsetTimeout(() => doWork('x'), 100);","handlingStrategy":"type-guard","validationCode":"if (typeof callback !== 'function') throw new TypeError('setTimeout callback must be a function');\nsetTimeout(callback, delayMs);","typeGuard":"/** @param {unknown} v @returns {v is Function} */\nconst isCallable = (v) => typeof v === 'function';\n\nif (!isCallable(maybeFn)) throw new TypeError(`expected function, got ${typeof maybeFn}`);","tryCatchPattern":null,"preventionTips":["Never port browser string-argument setTimeout code verbatim","Pass the reference, not the call result: setTimeout(fn) not setTimeout(fn())","Wrap arguments in a closure because k6's setTimeout does not forward them (source TODO)"],"tags":["settimeout","timers","experimental","type-validation"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}