{"record":{"id":"3c3bf9b2139e2919","repo":"grafana/k6","slug":"handler-for-q-event-isn-t-a-callable-function","errorCode":null,"errorMessage":"handler for %q event isn't a callable function","messagePattern":"handler for %q event isn't a callable function","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/grpc/stream.go","lineNumber":308,"sourceCode":"\t\t}\n\t}\n}\n\nfunc (s *stream) processSendError(err error) {\n\tif errors.Is(err, io.EOF) {\n\t\ts.logger.WithError(err).Debug(\"skip sending a message stream is cancelled/finished\")\n\t\terr = nil\n\t}\n\n\ts.tq.Queue(func() error {\n\t\treturn s.closeWithError(err)\n\t})\n}\n\n// on registers a handler for a certain event type\nfunc (s *stream) on(event string, handler func(sobek.Value, sobek.Value) (sobek.Value, error)) {\n\tif handler == nil {\n\t\tcommon.Throw(s.vu.Runtime(), fmt.Errorf(\"handler for %q event isn't a callable function\", event))\n\t}\n\n\tif err := s.eventListeners.add(event, handler); err != nil {\n\t\ts.vu.State().Logger.Warnf(\"can't register %s event handler: %s\", event, err)\n\t}\n}\n\n// write writes a message to the stream\nfunc (s *stream) write(input sobek.Value) {\n\tif s.writingState != opened {\n\t\treturn\n\t}\n\n\tif common.IsNullish(input) {\n\t\ts.logger.Warnf(\"can't send empty message\")\n\t\treturn\n\t}\n","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/grpc/stream.go#L290-L326","documentation":"Thrown by k6's gRPC stream API when stream.on(event, handler) is called with a handler that is not callable. Sobek converts the JS argument to the Go func(sobek.Value, sobek.Value) type; a non-function value (undefined, null, number, object) converts to nil, and on() (internal/js/modules/k6/grpc/stream.go:306-309) throws via common.Throw. Valid event types registered through eventListeners are 'data', 'error', and 'end'.","triggerScenarios":"stream.on('data', undefined) — typically a handler function whose import/definition is missing or misspelled; stream.on('end', { handle: fn }) (object instead of function); passing the result of calling the function fn() instead of the reference fn.","commonSituations":"Refactoring that renames or removes the handler but leaves the stream.on call; conditional imports (import { onData } from './handlers.js' where onData is not exported); copy-paste from docs where the handler placeholder was never replaced.","solutions":["Check the event name from the message and inspect the second argument at that call site — it must be a function reference.","Verify the handler is actually exported/defined: add console.log(typeof handler) before stream.on.","Pass the function reference (onData), not its invocation result (onData())."],"exampleFix":"// before\nimport { onMessage } from './handlers.js'; // not exported -> undefined\nstream.on('data', onMessage);\n\n// after\nimport { onMessage } from './handlers.js'; // export function onMessage(msg) {...}\nstream.on('data', onMessage);","handlingStrategy":"type-guard","validationCode":"const STREAM_EVENTS = new Set(['data', 'error', 'end']);\nfunction safeOn(stream, event, handler) {\n  if (!STREAM_EVENTS.has(event)) throw new Error(`unknown stream event '${event}'`);\n  if (typeof handler !== 'function') throw new Error(`handler for '${event}' must be a function, got ${typeof handler}`);\n  stream.on(event, handler);\n}","typeGuard":"const isCallable = (v) => typeof v === 'function';","tryCatchPattern":null,"preventionTips":["Assert typeof handler === 'function' during development with a tiny wrapper.","Import handlers from modules you own and re-export explicitly; avoid deep dynamic imports.","Lint for stream.on calls whose second argument is a literal or identifier that is never defined."],"tags":["grpc","streaming","event-handler","validation"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}