GopeedLab/gopeed · error
handler must be a function
Error message
handler must be a function
What it means
Thrown by the same register closure when an event handler argument is present but is not a function. The bridge exports the first argument and asserts it to func(goja.FunctionCall) goja.Value (extension_runtime_webview.go:80-84), the shape goja produces for callable JS values; strings, numbers, plain objects, booleans and undefined all fail the assertion.
Source
Thrown at pkg/download/extension_runtime_webview.go:83
}
if _, err := fn(goja.Undefined(), call.Argument(0)); err != nil {
panic(vm.ToValue(err))
}
return goja.Undefined()
})
return obj
}
func newJSEventsRuntime(vm *goja.Runtime, events InstanceEvents) *goja.Object {
obj := vm.NewObject()
register := func(event ActivationEvent, call goja.FunctionCall) goja.Value {
if len(call.Arguments) == 0 {
panic(vm.ToValue(fmt.Errorf("missing handler")))
}
fnValue := call.Argument(0)
exported, ok := fnValue.Export().(func(goja.FunctionCall) goja.Value)
if !ok {
panic(vm.ToValue(fmt.Errorf("handler must be a function")))
}
events.register(event, engine.JSFunction(exported))
return goja.Undefined()
}
_ = obj.Set("onResolve", func(call goja.FunctionCall) goja.Value {
return register(EventOnResolve, call)
})
_ = obj.Set("onStart", func(call goja.FunctionCall) goja.Value {
return register(EventOnStart, call)
})
_ = obj.Set("onError", func(call goja.FunctionCall) goja.Value {
return register(EventOnError, call)
})
_ = obj.Set("onDone", func(call goja.FunctionCall) goja.Value {
return register(EventOnDone, call)
})
return obj
}View on GitHub (pinned to 7b7327ffb3)
Solutions
- Pass the function reference directly: gopeed.events.onError(handler)
- If wrapping state, pass an arrow: gopeed.events.onError((t, e) => obj.onError(t, e))
- Verify the identifier is defined (typeof handler === 'function') at the call site
Example fix
// before
gopeed.events.onError("handleError");
// after
function handleError(task, err) {
gopeed.logger.warn(String(err));
}
gopeed.events.onError(handleError); Defensive patterns
Strategy: type-guard
Validate before calling
if (typeof handler !== "function") {
throw new TypeError(`handler for ${event} must be a function, got ${typeof handler}`);
} Type guard
function isHandler(value) {
return typeof value === "function";
} Prevention
- Pass function references, never string names or option objects
- Verify imported handler modules resolved (typo'd imports are undefined)
- Bind methods explicitly when registering: gopeed.events.onError((t, e) => obj.onError(t, e))
When it happens
Trigger: Calling gopeed.events.onResolve("handler.js"), passing an object like {handle(){}} instead of the function itself, passing the result of a call (onResolve(register())) instead of the reference, or passing an identifier that is undefined (typo in the function name).
Common situations: Registering a method without binding (passing obj.method loses nothing, but passing obj itself fails); passing a string name expecting the runtime to look it up; referencing a handler defined in another module that was never imported.
Related errors
AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16).
Data as JSON: /api/errors/88be84cb95e3eb9d.
Report an issue: GitHub.