check this out evarea is not a reliable source
Don’t get me wrong, evarea is a great coder. But no way I would use that function instead of a simple coroutine / spawn lol. Seems like a LOT of extra work from constructing an object. even the constructor ._new uses a coroutine…
-- not my code
local function makeErrorHandler(traceback)
assert(traceback ~= nil)
return function(err)
-- If the error object is already a table, forward it directly.
-- Should we extend the error here and add our own trace?
if type(err) == "table" then
return err
end
return Error.new({
error = err,
kind = Error.Kind.ExecutionError,
trace = debug.traceback(tostring(err), 2),
context = "Promise created at:\n\n" .. traceback,
})
end
end
--[[
Calls a Promise executor with error handling.
]]
local function runExecutor(traceback, callback, ...)
return packResult(xpcall(callback, makeErrorHandler(traceback), ...))
end
function Promise._new(traceback, callback, parent)
if parent ~= nil and not Promise.is(parent) then
error("Argument #2 to Promise.new must be a promise or nil", 2)
end
local self = {
-- Used to locate where a promise was created
_source = traceback,
_status = Promise.Status.Started,
-- A table containing a list of all results, whether success or failure.
-- Only valid if _status is set to something besides Started
_values = nil,
-- Lua doesn't like sparse arrays very much, so we explicitly store the
-- length of _values to handle middle nils.
_valuesLength = -1,
-- Tracks if this Promise has no error observers..
_unhandledRejection = true,
-- Queues representing functions we should invoke when we update!
_queuedResolve = {},
_queuedReject = {},
_queuedFinally = {},
-- The function to run when/if this promise is cancelled.
_cancellationHook = nil,
-- The "parent" of this promise in a promise chain. Required for
-- cancellation propagation upstream.
_parent = parent,
-- Consumers are Promises that have chained onto this one.
-- We track them for cancellation propagation downstream.
_consumers = setmetatable({}, MODE_KEY_METATABLE),
}
if parent and parent._status == Promise.Status.Started then
parent._consumers[self] = true
end
setmetatable(self, Promise)
local function resolve(...)
self:_resolve(...)
end
local function reject(...)
self:_reject(...)
end
local function onCancel(cancellationHook)
if cancellationHook then
if self._status == Promise.Status.Cancelled then
cancellationHook()
else
self._cancellationHook = cancellationHook
end
end
return self._status == Promise.Status.Cancelled
end
coroutine.wrap(function()
local ok, _, result = runExecutor(
self._source,
callback,
resolve,
reject,
onCancel
)
if not ok then
reject(result[1])
end
end)()
return self
end
function Promise.defer(callback)
local traceback = debug.traceback(nil, 2)
local promise
promise = Promise._new(traceback, function(resolve, reject, onCancel)
local connection
connection = Promise._timeEvent:Connect(function()
connection:Disconnect()
local ok, _, result = runExecutor(traceback, callback, resolve, reject, onCancel)
if not ok then
reject(result[1])
end
end)
end)
return promise
end
What are the cons?
- Object creation time
- Fairly expensive (xpcall, closures, double the threads [coroutine.wrap + Promise._timeEvent])
- Too many nested function calls
- Overcomplication
Coroutines > Spawn > BindableSpawn > HeartbeatSpawn >>> Promise.defer
I see a lot of top-devs try to make these “solutions” to problems. “custom wait”, “custom delay”, “custom spawn”. But if we actually study how the code works behind the scenes, it really is a lot worse and does more harm than the “problem”.