Why/when use custom signal modules?

I’ve seen quite a few of these modules going around but I can’t understand the real use of them.

Often the example I see is something like this:

local CustomSignal = require(game.ServerScriptService.CustomSignal) -- Adjust path as needed

local part = Instance.new("Part")
part.Size = Vector3.new(10, 1, 10)
part.Anchored = true
part.Position = Vector3.new(0, 0.5, 0)
part.Parent = game.Workspace

local playerTouchedSignal = CustomSignal.new()

playerTouchedSignal:Connect(function(player)
    print(player.Name .. " touched the part!")
end)

part.Touched:Connect(function(hit)
    local character = hit.Parent
    local player = game.Players:GetPlayerFromCharacter(character)
    
    if player then
        playerTouchedSignal:Fire(player)
    end
end)

But you could just have the signal be a function and call it? I don’t see how signals are useful.

I assume the module creates a bindable event that is attached to the function. A function call is normally blocking but firing a bindable / remote event (but not function) is non-blocking.

I was wondering this myself recently, this is what I found out.
Calling a function usually requires the current thread to yield and requires the called function to be in the same scope, using an event doesn’t.
Using a bindable event solves this issue but can be a pain to create and connect on the fly as it is an instance and needs parenting and connecting to via a path.
Signal type modules allow you to create and connect events without these constraints as the signal is usually in a module somewhere.