Edit:
If you’re reading this now, this feature has been implemented! xpcall supports yielding and lets you get traceback.
For example:
local function warnWithTracebackOnError(func, ...)
return xpcall(func, function(err)
warn(tostring(err) .. "\n" .. debug.traceback())
return false, err
end, ...)
end
-- On success: returns true, ...results
-- On error: warns, then returns false, err
Another example:
local function getMessageAndTracebackOnError(func, ...) -- similar to "tpcall" below
local traceback
local result = table.pack(
xpcall(func, function(err)
traceback = debug.traceback()
return false, err
end, ...)
)
if result[1] then
return unpack(result, 1, result.n)
else
return false, result[2], traceback
end
-- On success: returns true, ...results
-- On error: returns false, errMessage, traceback
Beyond that, I highly recommend using evaera’s Promise library – it has great support for error handling, making asynchronous code more clear, and it includes tracebacks with errors.
It lets you do things like:
Promise.try(function()
-- this might error, oh no!
error("some error")
end):catch(function(err)
if Promise.Error.is(err) then
warn("An error (" .. err.error .. ") occurred at " .. tostring(err.trace))
-- or you can just do:
warn(err)
-- which prints the error and traceback
end
end):expect()
With that said, if you want to see my old clever solution that you should absolutely not use anymore, you can find it below!
Original Post
I made a horrible work-around!
It uses ScriptContext.Error
, GUID names for modules, and BindableEvent
s to get tracebacks from functions.
This shouldn’t be necessary, but it was a fun challenge.
Here is the module.
Edit: Given the recent attention, I’d like to note that the module should be updated, but the below code is not! The module is public, so feel free to check it out!
It’s gone through a few revisions. Requiring modules can actually be slow and cause fps spikes, so the most recent version uses a cache of modules and only creates new ones if all of the cached ones are in use.
Because this uses cloned modules, it won’t work in roblox script security contexts such as BuiltInPlugins. Roblox seems to error on using cloned modules here as a security measure. It works fine in all normal user-accessible contexts, such as plugins, the command bar, server scripts, and client scripts.
Here is the source
tpcall
source
--[[
Use:
local success, error, traceback, fromScript = tpcall(func, args, ...)
--]]
local ScriptContext = game:GetService("ScriptContext")
local runnerBase = script.runner
return function(func, ...)
local uniqueId = game:GetService("HttpService"):GenerateGUID()
local runnerNew = runnerBase:Clone()
runnerNew.Name = uniqueId
local runner = require(runnerNew)
local bindableIn = Instance.new("BindableEvent")
local bindableOut = Instance.new("BindableEvent")
local inArgs = {...}
local success = true
local outArgs
bindableIn.Event:Connect(function()
outArgs = {runner(func, unpack(inArgs))}
bindableOut:Fire()
end)
local conn
conn = ScriptContext.Error:Connect(function(err, trace, scr)
if trace:find(uniqueId, 1, true) then
success = false
outArgs = {
err,
trace:match("^(.*)\n[^\n]+\n[^\n]+\n$"),
scr
}
bindableOut:fire()
end
end)
bindableIn:Fire()
if outArgs then
conn:disconnect()
bindableIn:Destroy()
bindableOut:Destroy()
return success, unpack(outArgs)
end
bindableOut.Event:wait()
conn:disconnect()
bindableIn:Destroy()
bindableOut:Destroy()
return success, unpack(outArgs)
end
runner
source
return function(func, ...)
return func(...)
end