Store length of _values explicitly.

This fixes some cases where middle-nils would drop values.
Not all cases are fixed because of the definition of wpcall,
which could be rectified by replacing it.
This commit is contained in:
Lucien Greathouse 2018-06-16 19:47:21 -07:00
parent bf3a3cf422
commit 6c6eef6a3b

View file

@ -6,11 +6,11 @@ local PROMISE_DEBUG = false
local function wpcall(f, ...) local function wpcall(f, ...)
local args = {...} local args = {...}
local argCount = select("#", ...) local argsLength = select("#", ...)
local result = { local result = {
xpcall(function() xpcall(function()
return f(unpack(args, 1, argCount)) return f(unpack(args, 1, argsLength))
end, debug.traceback) end, debug.traceback)
} }
@ -92,6 +92,10 @@ function Promise.new(callback)
-- Only valid if _status is set to something besides Started -- Only valid if _status is set to something besides Started
_values = nil, _values = nil,
-- Lua doesn't like sparse arrays very much, so we explicitly store the
-- length of _values to handle middle nils.
_valuesLength = -1,
-- If an error occurs with no observers, this will be set. -- If an error occurs with no observers, this will be set.
_unhandledRejection = false, _unhandledRejection = false,
@ -215,24 +219,27 @@ function Promise:await()
if self._status == Promise.Status.Started then if self._status == Promise.Status.Started then
local result local result
local resultLength
local bindable = Instance.new("BindableEvent") local bindable = Instance.new("BindableEvent")
self:andThen(function(...) self:andThen(function(...)
result = {...} result = {...}
resultLength = select("#", ...)
bindable:Fire(true) bindable:Fire(true)
end, function(...) end, function(...)
result = {...} result = {...}
resultLength = select("#", ...)
bindable:Fire(false) bindable:Fire(false)
end) end)
local ok = bindable.Event:Wait() local ok = bindable.Event:Wait()
bindable:Destroy() bindable:Destroy()
return ok, unpack(result) return ok, unpack(result, 1, resultLength)
elseif self._status == Promise.Status.Resolved then elseif self._status == Promise.Status.Resolved then
return true, unpack(self._values) return true, unpack(self._values, 1, self._valuesLength)
elseif self._status == Promise.Status.Rejected then elseif self._status == Promise.Status.Rejected then
return false, unpack(self._values) return false, unpack(self._values, 1, self._valuesLength)
end end
end end
@ -241,10 +248,12 @@ function Promise:_resolve(...)
return return
end end
local argLength = select("#", ...)
-- If the resolved value was a Promise, we chain onto it! -- If the resolved value was a Promise, we chain onto it!
if Promise.is((...)) then if Promise.is((...)) then
-- Without this warning, arguments sometimes mysteriously disappear -- Without this warning, arguments sometimes mysteriously disappear
if select("#", ...) > 1 then if argLength > 1 then
local message = ( local message = (
"When returning a Promise from andThen, extra arguments are " .. "When returning a Promise from andThen, extra arguments are " ..
"discarded! See:\n\n%s" "discarded! See:\n\n%s"
@ -265,6 +274,7 @@ function Promise:_resolve(...)
self._status = Promise.Status.Resolved self._status = Promise.Status.Resolved
self._values = {...} self._values = {...}
self._valuesLength = argLength
-- We assume that these callbacks will not throw errors. -- We assume that these callbacks will not throw errors.
for _, callback in ipairs(self._queuedResolve) do for _, callback in ipairs(self._queuedResolve) do
@ -279,6 +289,7 @@ function Promise:_reject(...)
self._status = Promise.Status.Rejected self._status = Promise.Status.Rejected
self._values = {...} self._values = {...}
self._valuesLength = select("#", ...)
-- If there are any rejection handlers, call those! -- If there are any rejection handlers, call those!
if not isEmpty(self._queuedReject) then if not isEmpty(self._queuedReject) then