mirror of
https://github.com/AmberGraceRblx/luau-promise.git
synced 2025-04-24 15:50:01 +00:00
Improve stack traces and errors
This commit is contained in:
parent
f541fb7202
commit
65daff4777
1 changed files with 119 additions and 25 deletions
144
lib/init.lua
144
lib/init.lua
|
@ -6,6 +6,7 @@ local ERROR_YIELD_NEW = "Yielding inside Promise.new is not allowed! Use Promise
|
|||
local ERROR_YIELD_THEN = "Yielding inside andThen/catch is not allowed! Instead, return a new Promise from andThen/catch."
|
||||
local ERROR_NON_PROMISE_IN_LIST = "Non-promise value passed into %s at index %s"
|
||||
local ERROR_NON_LIST = "Please pass a list of promises to %s"
|
||||
local ERROR_NON_FUNCTION = "Please pass a handler function to %s!"
|
||||
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
|
@ -44,8 +45,6 @@ local function ppcall(yieldError, callback, ...)
|
|||
|
||||
if ok and coroutine.status(co) ~= "dead" then
|
||||
error(yieldError, 2)
|
||||
elseif not ok then
|
||||
result[1] = debug.traceback(result[1], 2)
|
||||
end
|
||||
|
||||
return ok, len, result
|
||||
|
@ -55,14 +54,14 @@ end
|
|||
Creates a function that invokes a callback with correct error handling and
|
||||
resolution mechanisms.
|
||||
]]
|
||||
local function createAdvancer(callback, resolve, reject)
|
||||
local function createAdvancer(traceback, callback, resolve, reject)
|
||||
return function(...)
|
||||
local ok, resultLength, result = ppcall(ERROR_YIELD_THEN, callback, ...)
|
||||
|
||||
if ok then
|
||||
resolve(unpack(result, 1, resultLength))
|
||||
else
|
||||
reject(unpack(result, 1, resultLength))
|
||||
reject(result[1], traceback)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -109,6 +108,9 @@ function Promise.new(callback, parent)
|
|||
|
||||
_status = Promise.Status.Started,
|
||||
|
||||
-- Will be set to the Lua error string if it occurs while executing.
|
||||
_error = nil,
|
||||
|
||||
-- A table containing a list of all results, whether success or failure.
|
||||
-- Only valid if _status is set to something besides Started
|
||||
_values = nil,
|
||||
|
@ -163,32 +165,66 @@ function Promise.new(callback, parent)
|
|||
return self._status == Promise.Status.Cancelled
|
||||
end
|
||||
|
||||
local ok, _, result = ppcall(ERROR_YIELD_NEW, callback, resolve, reject, onCancel)
|
||||
local err = result[1]
|
||||
local ok, _, result = ppcall(
|
||||
ERROR_YIELD_NEW,
|
||||
callback,
|
||||
resolve,
|
||||
reject,
|
||||
onCancel
|
||||
)
|
||||
|
||||
if not ok and self._status == Promise.Status.Started then
|
||||
reject(err)
|
||||
if not ok then
|
||||
self._error = result[1] or "error"
|
||||
reject((result[1] or "error") .. "\n" .. self._source)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Promise._newWithSelf(executor, ...)
|
||||
local args
|
||||
local promise = Promise.new(function(...)
|
||||
args = {...}
|
||||
end, ...)
|
||||
|
||||
executor(promise, unpack(args))
|
||||
|
||||
return promise
|
||||
end
|
||||
|
||||
function Promise._new(traceback, executor, ...)
|
||||
return Promise._newWithSelf(function(self, resolve, reject)
|
||||
self._source = traceback
|
||||
|
||||
executor(resolve, function(err, traceback)
|
||||
err = err or "error"
|
||||
traceback = traceback or ""
|
||||
self._error = err
|
||||
reject(err .. "\n" .. traceback)
|
||||
end)
|
||||
end, ...)
|
||||
end
|
||||
|
||||
--[[
|
||||
Promise.new, except pcall on a new thread is automatic.
|
||||
]]
|
||||
function Promise.async(callback)
|
||||
local traceback = debug.traceback()
|
||||
return Promise.new(function(resolve, reject, onCancel)
|
||||
local promise
|
||||
promise = Promise.new(function(resolve, reject, onCancel)
|
||||
local connection
|
||||
connection = RunService.Heartbeat:Connect(function()
|
||||
connection:Disconnect()
|
||||
local ok, err = pcall(callback, resolve, reject, onCancel)
|
||||
|
||||
if not ok then
|
||||
promise._error = err or "error"
|
||||
reject(err .. "\n" .. traceback)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
return promise
|
||||
end
|
||||
|
||||
--[[
|
||||
|
@ -350,22 +386,32 @@ end
|
|||
|
||||
The given callbacks are invoked depending on that result.
|
||||
]]
|
||||
function Promise.prototype:andThen(successHandler, failureHandler)
|
||||
function Promise.prototype:_andThen(traceback, successHandler, failureHandler)
|
||||
self._unhandledRejection = false
|
||||
|
||||
-- Create a new promise to follow this part of the chain
|
||||
return Promise.new(function(resolve, reject)
|
||||
return Promise._new(traceback, function(resolve, reject)
|
||||
-- Our default callbacks just pass values onto the next promise.
|
||||
-- This lets success and failure cascade correctly!
|
||||
|
||||
local successCallback = resolve
|
||||
if successHandler then
|
||||
successCallback = createAdvancer(successHandler, resolve, reject)
|
||||
successCallback = createAdvancer(
|
||||
traceback,
|
||||
successHandler,
|
||||
resolve,
|
||||
reject
|
||||
)
|
||||
end
|
||||
|
||||
local failureCallback = reject
|
||||
if failureHandler then
|
||||
failureCallback = createAdvancer(failureHandler, resolve, reject)
|
||||
failureCallback = createAdvancer(
|
||||
traceback,
|
||||
failureHandler,
|
||||
resolve,
|
||||
reject
|
||||
)
|
||||
end
|
||||
|
||||
if self._status == Promise.Status.Started then
|
||||
|
@ -386,11 +432,28 @@ function Promise.prototype:andThen(successHandler, failureHandler)
|
|||
end, self)
|
||||
end
|
||||
|
||||
function Promise.prototype:andThen(successHandler, failureHandler)
|
||||
assert(
|
||||
successHandler == nil or type(successHandler) == "function",
|
||||
ERROR_NON_FUNCTION:format("Promise:andThen")
|
||||
)
|
||||
assert(
|
||||
failureHandler == nil or type(failureHandler) == "function",
|
||||
ERROR_NON_FUNCTION:format("Promise:andThen")
|
||||
)
|
||||
|
||||
return self:_andThen(debug.traceback(), successHandler, failureHandler)
|
||||
end
|
||||
|
||||
--[[
|
||||
Used to catch any errors that may have occurred in the promise.
|
||||
]]
|
||||
function Promise.prototype:catch(failureCallback)
|
||||
return self:andThen(nil, failureCallback)
|
||||
assert(
|
||||
failureCallback == nil or type(failureCallback) == "function",
|
||||
ERROR_NON_FUNCTION:format("Promise:catch")
|
||||
)
|
||||
return self:_andThen(debug.traceback(), nil, failureCallback)
|
||||
end
|
||||
|
||||
--[[
|
||||
|
@ -398,7 +461,8 @@ end
|
|||
value returned from the handler.
|
||||
]]
|
||||
function Promise.prototype:tap(tapCallback)
|
||||
return self:andThen(function(...)
|
||||
assert(type(tapCallback) == "function", ERROR_NON_FUNCTION:format("Promise:tap"))
|
||||
return self:_andThen(debug.traceback(), function(...)
|
||||
local callbackReturn = tapCallback(...)
|
||||
|
||||
if Promise.is(callbackReturn) then
|
||||
|
@ -416,8 +480,9 @@ end
|
|||
Calls a callback on `andThen` with specific arguments.
|
||||
]]
|
||||
function Promise.prototype:andThenCall(callback, ...)
|
||||
assert(type(callback) == "function", ERROR_NON_FUNCTION:format("Promise:andThenCall"))
|
||||
local length, values = pack(...)
|
||||
return self:andThen(function()
|
||||
return self:_andThen(debug.traceback(), function()
|
||||
return callback(unpack(values, 1, length))
|
||||
end)
|
||||
end
|
||||
|
@ -468,14 +533,19 @@ end
|
|||
Used to set a handler for when the promise resolves, rejects, or is
|
||||
cancelled. Returns a new promise chained from this promise.
|
||||
]]
|
||||
function Promise.prototype:finally(finallyHandler)
|
||||
function Promise.prototype:_finally(traceback, finallyHandler)
|
||||
self._unhandledRejection = false
|
||||
|
||||
-- Return a promise chained off of this promise
|
||||
return Promise.new(function(resolve, reject)
|
||||
return Promise._new(traceback, function(resolve, reject)
|
||||
local finallyCallback = resolve
|
||||
if finallyHandler then
|
||||
finallyCallback = createAdvancer(finallyHandler, resolve, reject)
|
||||
finallyCallback = createAdvancer(
|
||||
traceback,
|
||||
finallyHandler,
|
||||
resolve,
|
||||
reject
|
||||
)
|
||||
end
|
||||
|
||||
if self._status == Promise.Status.Started then
|
||||
|
@ -488,12 +558,21 @@ function Promise.prototype:finally(finallyHandler)
|
|||
end, self)
|
||||
end
|
||||
|
||||
function Promise.prototype:finally(finallyHandler)
|
||||
assert(
|
||||
finallyHandler == nil or type(finallyHandler) == "function",
|
||||
ERROR_NON_FUNCTION:format("Promise:finally")
|
||||
)
|
||||
return self:_finally(debug.traceback(), finallyHandler)
|
||||
end
|
||||
|
||||
--[[
|
||||
Calls a callback on `finally` with specific arguments.
|
||||
]]
|
||||
function Promise.prototype:finallyCall(callback, ...)
|
||||
assert(type(callback) == "function", ERROR_NON_FUNCTION:format("Promise:finallyCall"))
|
||||
local length, values = pack(...)
|
||||
return self:finally(function()
|
||||
return self:_finally(debug.traceback(), function()
|
||||
return callback(unpack(values, 1, length))
|
||||
end)
|
||||
end
|
||||
|
@ -590,11 +669,17 @@ function Promise.prototype:_resolve(...)
|
|||
warn(message)
|
||||
end
|
||||
|
||||
local promise = (...):andThen(
|
||||
local chainedPromise = ...
|
||||
|
||||
local promise = chainedPromise:andThen(
|
||||
function(...)
|
||||
self:_resolve(...)
|
||||
end,
|
||||
function(...)
|
||||
-- The handler errored. Replace the inner stack trace with our outer stack trace.
|
||||
if chainedPromise._error then
|
||||
return self:_reject((chainedPromise._error or "") .. "\n" .. self._source)
|
||||
end
|
||||
self:_reject(...)
|
||||
end
|
||||
)
|
||||
|
@ -652,10 +737,15 @@ function Promise.prototype:_reject(...)
|
|||
end
|
||||
|
||||
-- Build a reasonable message
|
||||
local message = ("Unhandled promise rejection:\n\n%s\n\n%s"):format(
|
||||
err,
|
||||
self._source
|
||||
)
|
||||
local message
|
||||
if self._error then
|
||||
message = ("Unhandled promise rejection:\n\n%s"):format(err)
|
||||
else
|
||||
message = ("Unhandled promise rejection:\n\n%s\n\n%s"):format(
|
||||
err,
|
||||
self._source
|
||||
)
|
||||
end
|
||||
warn(message)
|
||||
end)()
|
||||
end
|
||||
|
@ -676,6 +766,10 @@ function Promise.prototype:_finalize()
|
|||
callback(self._status)
|
||||
end
|
||||
|
||||
if self._parent and self._error == nil then
|
||||
self._error = self._parent._error
|
||||
end
|
||||
|
||||
-- Allow family to be buried
|
||||
if not Promise.TEST then
|
||||
self._parent = nil
|
||||
|
|
Loading…
Reference in a new issue