For me the problem with coroutine.wrap is that the wrapped function call can cause the calling thread to be terminated if it errors before it yields.
Here is a code example that illustrates the problem:
print("This does NOT print")
This will never happen when you call coroutine.resume, it is safer and makes protected function calls.
We do however have to make sure that we manually catch errors when we call coroutine.resume.
Here is my FastSpawn implementation that catches errors without terminating the calling thread:
function Thread.LogAsError(message: string, stacktrace: string): ()
--@NOTE: Errors printed by TestService will not be caught by the ScriptContext.Error event.
--So in order for any error logging services to work properly, they need to be manually invoked here.
TestService:Message("\n" .. stacktrace)
function Thread.FastSpawn(func: any, ...: any): ()
local thread = coroutine.create(func)
local ok, msg = coroutine.resume(thread, ...)
if(not ok) then Thread.LogAsError(msg, debug.traceback(thread) .. debug.traceback(nil, 2)) end