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

  1. Pass the function reference directly: gopeed.events.onError(handler)
  2. If wrapping state, pass an arrow: gopeed.events.onError((t, e) => obj.onError(t, e))
  3. 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

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.