Making a custom event handler, how should I go about it?

So I wrote an event handler for my OOP library, (Made a custom Event class using it), and now I’m thinking there may be a faster way of going about it. Atm it just loops through all the connected functions:

control.Fire = function(self, ...)
		if self ~= object and self ~= control then -- Enforce ':', There is no need for it, but its for consistency.
			error("Expected ':' not '.' calling member function Fire", 2);
		end
		if connected == nil then return end; -- Disallows firing or connecting when the object has been :Destroy()

		for i = 1, #connected, 1 do
			coroutine.wrap(function(...)
				local success, response = pcall(connected[1], ...);
				if success == false then
					error(response, 0); -- I wrap this so there's no weird stack tracing from the coroutine.
				end
			end)(...);
		end
	end

So, I was wondering if there’s a faster way to go about this? Kinda like how clonetrooper made a faster wait function, even if it was only milliseconds faster

Don’t worry about speed much in ScriptSignals.
Every signal class out there is gonna be fast enough for every use case.

Your simple event implementation here, I’m trying to understand why the coroutine.wrap and all that pcall work? Both of these methods create threads, and are quite expensive, I don’t see any reason to wrap stuff so much?

Anyway, it’s literally just a question of using task.spawn for spawning your methods and everything will be right for you.

Using arrays also have their own issues when you’re not deferring events, some handlers under specific scenarios can end up not firing when RBXScriptSignals would.

Building an event API from the start, there’s a lot of weird unexpected behaviour that comes with it, so I recommend using someone’s else work for that. Saves a lot of headaches.

I personally have my own Signal API which is one of my favourite libraries that I made, which is FastSignal, which has extremely familiar behaviour to RBXScriptSignals, there’s also GoodSignal which greatly helped me build mine too, and it’s also a great option. Though be ware that if you want .Connected you will need to modify it.

task.spawn can’t take parameters. Also, I wrap coroutine so I can error the error without yielding. I use pcall so I can have proper stack tracing in the error itself

If only debug.sethook() was available.

It can.

It’s parameters are thread | function, ...any
The first parameter is either a thread you want to resume, or a function which you wanna create a new thread to, the rest of the parameters are the arguments you want to resume the thread with / what the function is called with.

Also, coroutine.wrap even then, doesn’t need a pcall to get a stack trace, at least if my understanding of stack trace is right, this was changed in Luau a while ago. It at least, in my experience I’ve had no issues, still, use task.spawn.

If you really for some reason wanna stick with your weird pcall solution, you can make it not create a new function for each connection.

local function PcallWrap(handler, ...)
    local success, errorMessage = pcall(handler, ...)
    if success == false then
        error(errorMessage)
    end
end

-- on Fire

coroutine.wrap(PcallWrap)(connection._handler, ...)

But like I said, it’s not needed, task.spawn does the job.