mirror of
https://github.com/AmberGraceRblx/luau-promise.git
synced 2025-04-25 08:00:03 +00:00
Expose & refactor RuntimeError
This commit is contained in:
parent
292e47293c
commit
ff345ea31b
2 changed files with 75 additions and 25 deletions
82
lib/init.lua
82
lib/init.lua
|
@ -17,29 +17,55 @@ local RunService = game:GetService("RunService")
|
||||||
Promises that experience an error like this will be rejected with
|
Promises that experience an error like this will be rejected with
|
||||||
an instance of this object.
|
an instance of this object.
|
||||||
]]
|
]]
|
||||||
local PromiseRuntimeError = {}
|
local RuntimeError = {}
|
||||||
PromiseRuntimeError.__index = PromiseRuntimeError
|
RuntimeError.__index = RuntimeError
|
||||||
|
|
||||||
function PromiseRuntimeError.new(errorString)
|
function RuntimeError.new(options, parent)
|
||||||
|
options = options or {}
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
errorString = tostring(errorString) or "[This error has no error text.]"
|
error = tostring(options.error) or "[This error has no error text.]",
|
||||||
}, PromiseRuntimeError)
|
trace = options.trace,
|
||||||
|
context = options.context,
|
||||||
|
parent = parent,
|
||||||
|
createdTick = tick(),
|
||||||
|
createdTrace = debug.traceback()
|
||||||
|
}, RuntimeError)
|
||||||
end
|
end
|
||||||
|
|
||||||
function PromiseRuntimeError.is(anything)
|
function RuntimeError.is(anything)
|
||||||
if type(anything) == "table" then
|
if type(anything) == "table" then
|
||||||
return rawget(anything, "errorString") ~= nil
|
local metatable = getmetatable(anything)
|
||||||
|
|
||||||
|
if type(metatable) == "table" then
|
||||||
|
return rawget(anything, "error") ~= nil and type(rawget(metatable, "extend")) == "function"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
function PromiseRuntimeError:extend(errorString)
|
function RuntimeError:extend(options)
|
||||||
return PromiseRuntimeError.new(("%s\n%s"):format(tostring(errorString), self.errorString))
|
return RuntimeError.new(options, self)
|
||||||
end
|
end
|
||||||
|
|
||||||
function PromiseRuntimeError:__tostring()
|
function RuntimeError:getErrorChain()
|
||||||
return self.errorString
|
local runtimeErrors = { self }
|
||||||
|
|
||||||
|
while runtimeErrors[#runtimeErrors].parent do
|
||||||
|
table.insert(runtimeErrors, runtimeErrors[#runtimeErrors].parent)
|
||||||
|
end
|
||||||
|
|
||||||
|
return runtimeErrors
|
||||||
|
end
|
||||||
|
|
||||||
|
function RuntimeError:__tostring()
|
||||||
|
local errorStrings = {}
|
||||||
|
|
||||||
|
for _, runtimeError in ipairs(self:getErrorChain()) do
|
||||||
|
table.insert(errorStrings, table.concat({runtimeError.trace or runtimeError.error, runtimeError.context}, "\n"))
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.concat(errorStrings, "\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
|
@ -61,7 +87,11 @@ end
|
||||||
|
|
||||||
local function makeErrorHandler(traceback)
|
local function makeErrorHandler(traceback)
|
||||||
return function(err)
|
return function(err)
|
||||||
return debug.traceback(err, 2) .. "\nPromise created at:\n\n" .. traceback
|
return RuntimeError.new({
|
||||||
|
error = err,
|
||||||
|
trace = debug.traceback(err, 2),
|
||||||
|
context = "Promise created at:\n\n" .. traceback
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -83,7 +113,7 @@ local function createAdvancer(traceback, callback, resolve, reject)
|
||||||
if ok then
|
if ok then
|
||||||
resolve(unpack(result, 1, resultLength))
|
resolve(unpack(result, 1, resultLength))
|
||||||
else
|
else
|
||||||
reject(PromiseRuntimeError.new(result[1]))
|
reject(result[1])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -93,8 +123,9 @@ local function isEmpty(t)
|
||||||
end
|
end
|
||||||
|
|
||||||
local Promise = {
|
local Promise = {
|
||||||
|
RuntimeError = RuntimeError,
|
||||||
_timeEvent = RunService.Heartbeat,
|
_timeEvent = RunService.Heartbeat,
|
||||||
_getTime = tick
|
_getTime = tick,
|
||||||
}
|
}
|
||||||
Promise.prototype = {}
|
Promise.prototype = {}
|
||||||
Promise.__index = Promise.prototype
|
Promise.__index = Promise.prototype
|
||||||
|
@ -197,7 +228,7 @@ function Promise._new(traceback, callback, parent)
|
||||||
)
|
)
|
||||||
|
|
||||||
if not ok then
|
if not ok then
|
||||||
reject(PromiseRuntimeError.new(result[1]))
|
reject(result[1])
|
||||||
end
|
end
|
||||||
end)()
|
end)()
|
||||||
|
|
||||||
|
@ -225,7 +256,7 @@ function Promise.defer(callback)
|
||||||
local ok, _, result = runExecutor(traceback, callback, resolve, reject, onCancel)
|
local ok, _, result = runExecutor(traceback, callback, resolve, reject, onCancel)
|
||||||
|
|
||||||
if not ok then
|
if not ok then
|
||||||
reject(PromiseRuntimeError.new(result))
|
reject(result[1])
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
@ -1000,19 +1031,24 @@ function Promise.prototype:_resolve(...)
|
||||||
self:_resolve(...)
|
self:_resolve(...)
|
||||||
end,
|
end,
|
||||||
function(...)
|
function(...)
|
||||||
local runtimeError = chainedPromise._values[1]
|
local maybeRuntimeError = chainedPromise._values[1]
|
||||||
|
|
||||||
-- Backwards compatibility <v2
|
-- Backwards compatibility < v2
|
||||||
if chainedPromise._error then
|
if chainedPromise._error then
|
||||||
runtimeError = PromiseRuntimeError.new(chainedPromise._error)
|
maybeRuntimeError = RuntimeError.new({
|
||||||
|
error = chainedPromise._error,
|
||||||
|
context = "[No stack trace available as this Promise originated from an older version of the Promise library (< v2)]"
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
if PromiseRuntimeError.is(runtimeError) then
|
if RuntimeError.is(maybeRuntimeError) then
|
||||||
return self:_reject(runtimeError:extend(
|
return self:_reject(maybeRuntimeError:extend({
|
||||||
("The Promise at:\n\n%s\n...Rejected because it was chained to the following Promise, which encountered an error:\n"):format(
|
error = "This Promise was chained to a Promise that errored.",
|
||||||
|
trace = "",
|
||||||
|
context = ("The Promise at:\n\n%s\n...Rejected because it was chained to the following Promise, which encountered an error:\n"):format(
|
||||||
self._source
|
self._source
|
||||||
)
|
)
|
||||||
))
|
}))
|
||||||
end
|
end
|
||||||
|
|
||||||
self:_reject(...)
|
self:_reject(...)
|
||||||
|
|
|
@ -147,6 +147,20 @@ return function()
|
||||||
expect(trace:find("runPlanNode")).to.be.ok()
|
expect(trace:find("runPlanNode")).to.be.ok()
|
||||||
expect(trace:find("...Rejected because it was chained to the following Promise, which encountered an error:")).to.be.ok()
|
expect(trace:find("...Rejected because it was chained to the following Promise, which encountered an error:")).to.be.ok()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("should report errors from Promises with _error (< v2)", function()
|
||||||
|
local oldPromise = Promise.reject()
|
||||||
|
oldPromise._error = "Sample error"
|
||||||
|
|
||||||
|
local newPromise = Promise.resolve():andThenReturn(oldPromise)
|
||||||
|
|
||||||
|
expect(newPromise:getStatus()).to.equal(Promise.Status.Rejected)
|
||||||
|
|
||||||
|
local trace = tostring(newPromise._values[1])
|
||||||
|
expect(trace:find("Sample error")).to.be.ok()
|
||||||
|
expect(trace:find("...Rejected because it was chained to the following Promise, which encountered an error:")).to.be.ok()
|
||||||
|
expect(trace:find("%[No stack trace available")).to.be.ok()
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("Promise.defer", function()
|
describe("Promise.defer", function()
|
||||||
|
|
Loading…
Reference in a new issue