Support callable tables where functions are allowed

Closes #64
This commit is contained in:
eryn L. K 2021-12-27 21:43:37 -05:00
parent e7033308ec
commit 4e04458816
2 changed files with 169 additions and 141 deletions

View file

@ -7,6 +7,21 @@ local ERROR_NON_LIST = "Please pass a list of promises to %s"
local ERROR_NON_FUNCTION = "Please pass a handler function to %s!" local ERROR_NON_FUNCTION = "Please pass a handler function to %s!"
local MODE_KEY_METATABLE = { __mode = "k" } local MODE_KEY_METATABLE = { __mode = "k" }
local function isCallable(value)
if type(value) == "function" then
return true
end
if type(value) == "table" then
local metatable = getmetatable(value)
if metatable and type(rawget(metatable, "__call")) == "function" then
return true
end
end
return false
end
--[[ --[[
Creates an enum dictionary with some metamethods to prevent common mistakes. Creates an enum dictionary with some metamethods to prevent common mistakes.
]] ]]
@ -131,7 +146,7 @@ local function packResult(success, ...)
end end
local function makeErrorHandler(traceback) local function makeErrorHandler(traceback)
assert(traceback ~= nil) assert(traceback ~= nil, "traceback is nil")
return function(err) return function(err)
-- If the error object is already a table, forward it directly. -- If the error object is already a table, forward it directly.
@ -592,7 +607,7 @@ end
]=] ]=]
function Promise.fold(list, reducer, initialValue) function Promise.fold(list, reducer, initialValue)
assert(type(list) == "table", "Bad argument #1 to Promise.fold: must be a table") assert(type(list) == "table", "Bad argument #1 to Promise.fold: must be a table")
assert(type(reducer) == "function", "Bad argument #2 to Promise.fold: must be a function") assert(isCallable(reducer), "Bad argument #2 to Promise.fold: must be a function")
local accumulator = Promise.resolve(initialValue) local accumulator = Promise.resolve(initialValue)
return Promise.each(list, function(resolvedElement, i) return Promise.each(list, function(resolvedElement, i)
@ -842,7 +857,7 @@ end
]=] ]=]
function Promise.each(list, predicate) function Promise.each(list, predicate)
assert(type(list) == "table", string.format(ERROR_NON_LIST, "Promise.each")) assert(type(list) == "table", string.format(ERROR_NON_LIST, "Promise.each"))
assert(type(predicate) == "function", string.format(ERROR_NON_FUNCTION, "Promise.each")) assert(isCallable(predicate), string.format(ERROR_NON_FUNCTION, "Promise.each"))
return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel) return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel)
local results = {} local results = {}
@ -951,11 +966,11 @@ function Promise.is(object)
return true return true
elseif objectMetatable == nil then elseif objectMetatable == nil then
-- No metatable, but we should still chain onto tables with andThen methods -- No metatable, but we should still chain onto tables with andThen methods
return type(object.andThen) == "function" return isCallable(object.andThen)
elseif elseif
type(objectMetatable) == "table" type(objectMetatable) == "table"
and type(rawget(objectMetatable, "__index")) == "table" and type(rawget(objectMetatable, "__index")) == "table"
and type(rawget(rawget(objectMetatable, "__index"), "andThen")) == "function" and isCallable(rawget(rawget(objectMetatable, "__index"), "andThen"))
then then
-- Maybe this came from a different or older Promise library. -- Maybe this came from a different or older Promise library.
return true return true
@ -1235,14 +1250,8 @@ end
@return Promise<...any> @return Promise<...any>
]=] ]=]
function Promise.prototype:andThen(successHandler, failureHandler) function Promise.prototype:andThen(successHandler, failureHandler)
assert( assert(successHandler == nil or isCallable(successHandler), string.format(ERROR_NON_FUNCTION, "Promise:andThen"))
successHandler == nil or type(successHandler) == "function", assert(failureHandler == nil or isCallable(failureHandler), string.format(ERROR_NON_FUNCTION, "Promise:andThen"))
string.format(ERROR_NON_FUNCTION, "Promise:andThen")
)
assert(
failureHandler == nil or type(failureHandler) == "function",
string.format(ERROR_NON_FUNCTION, "Promise:andThen")
)
return self:_andThen(debug.traceback(nil, 2), successHandler, failureHandler) return self:_andThen(debug.traceback(nil, 2), successHandler, failureHandler)
end end
@ -1261,10 +1270,7 @@ end
@return Promise<...any> @return Promise<...any>
]=] ]=]
function Promise.prototype:catch(failureHandler) function Promise.prototype:catch(failureHandler)
assert( assert(failureHandler == nil or isCallable(failureHandler), string.format(ERROR_NON_FUNCTION, "Promise:catch"))
failureHandler == nil or type(failureHandler) == "function",
string.format(ERROR_NON_FUNCTION, "Promise:catch")
)
return self:_andThen(debug.traceback(nil, 2), nil, failureHandler) return self:_andThen(debug.traceback(nil, 2), nil, failureHandler)
end end
@ -1285,7 +1291,7 @@ end
@return Promise<...any> @return Promise<...any>
]=] ]=]
function Promise.prototype:tap(tapHandler) function Promise.prototype:tap(tapHandler)
assert(type(tapHandler) == "function", string.format(ERROR_NON_FUNCTION, "Promise:tap")) assert(isCallable(tapHandler), string.format(ERROR_NON_FUNCTION, "Promise:tap"))
return self:_andThen(debug.traceback(nil, 2), function(...) return self:_andThen(debug.traceback(nil, 2), function(...)
local callbackReturn = tapHandler(...) local callbackReturn = tapHandler(...)
@ -1320,7 +1326,7 @@ end
@return Promise @return Promise
]=] ]=]
function Promise.prototype:andThenCall(callback, ...) function Promise.prototype:andThenCall(callback, ...)
assert(type(callback) == "function", string.format(ERROR_NON_FUNCTION, "Promise:andThenCall")) assert(isCallable(callback), string.format(ERROR_NON_FUNCTION, "Promise:andThenCall"))
local length, values = pack(...) local length, values = pack(...)
return self:_andThen(debug.traceback(nil, 2), function() return self:_andThen(debug.traceback(nil, 2), function()
return callback(unpack(values, 1, length)) return callback(unpack(values, 1, length))
@ -1474,10 +1480,7 @@ end
@return Promise<...any> @return Promise<...any>
]=] ]=]
function Promise.prototype:finally(finallyHandler) function Promise.prototype:finally(finallyHandler)
assert( assert(finallyHandler == nil or isCallable(finallyHandler), string.format(ERROR_NON_FUNCTION, "Promise:finally"))
finallyHandler == nil or type(finallyHandler) == "function",
string.format(ERROR_NON_FUNCTION, "Promise:finally")
)
return self:_finally(debug.traceback(nil, 2), finallyHandler) return self:_finally(debug.traceback(nil, 2), finallyHandler)
end end
@ -1491,7 +1494,7 @@ end
@return Promise @return Promise
]=] ]=]
function Promise.prototype:finallyCall(callback, ...) function Promise.prototype:finallyCall(callback, ...)
assert(type(callback) == "function", string.format(ERROR_NON_FUNCTION, "Promise:finallyCall")) assert(isCallable(callback), string.format(ERROR_NON_FUNCTION, "Promise:finallyCall"))
local length, values = pack(...) local length, values = pack(...)
return self:_finally(debug.traceback(nil, 2), function() return self:_finally(debug.traceback(nil, 2), function()
return callback(unpack(values, 1, length)) return callback(unpack(values, 1, length))
@ -1540,7 +1543,7 @@ end
@return Promise<...any> @return Promise<...any>
]=] ]=]
function Promise.prototype:done(doneHandler) function Promise.prototype:done(doneHandler)
assert(doneHandler == nil or type(doneHandler) == "function", string.format(ERROR_NON_FUNCTION, "Promise:done")) assert(doneHandler == nil or isCallable(doneHandler), string.format(ERROR_NON_FUNCTION, "Promise:done"))
return self:_finally(debug.traceback(nil, 2), doneHandler, true) return self:_finally(debug.traceback(nil, 2), doneHandler, true)
end end
@ -1554,7 +1557,7 @@ end
@return Promise @return Promise
]=] ]=]
function Promise.prototype:doneCall(callback, ...) function Promise.prototype:doneCall(callback, ...)
assert(type(callback) == "function", string.format(ERROR_NON_FUNCTION, "Promise:doneCall")) assert(isCallable(callback), string.format(ERROR_NON_FUNCTION, "Promise:doneCall"))
local length, values = pack(...) local length, values = pack(...)
return self:_finally(debug.traceback(nil, 2), function() return self:_finally(debug.traceback(nil, 2), function()
return callback(unpack(values, 1, length)) return callback(unpack(values, 1, length))
@ -1903,7 +1906,7 @@ end
@param ...? P @param ...? P
]=] ]=]
function Promise.retry(callback, times, ...) function Promise.retry(callback, times, ...)
assert(type(callback) == "function", "Parameter #1 to Promise.retry must be a function") assert(isCallable(callback), "Parameter #1 to Promise.retry must be a function")
assert(type(times) == "number", "Parameter #2 to Promise.retry must be a number") assert(type(times) == "number", "Parameter #2 to Promise.retry must be a number")
local args, length = { ... }, select("#", ...) local args, length = { ... }, select("#", ...)

View file

@ -5,7 +5,8 @@ return function()
local timeEvent = Instance.new("BindableEvent") local timeEvent = Instance.new("BindableEvent")
Promise._timeEvent = timeEvent.Event Promise._timeEvent = timeEvent.Event
local advanceTime do local advanceTime
do
local injectedPromiseTime = 0 local injectedPromiseTime = 0
Promise._getTime = function() Promise._getTime = function()
@ -13,7 +14,7 @@ return function()
end end
function advanceTime(delta) function advanceTime(delta)
delta = delta or (1/60) delta = delta or (1 / 60)
injectedPromiseTime = injectedPromiseTime + delta injectedPromiseTime = injectedPromiseTime + delta
timeEvent:Fire(delta) timeEvent:Fire(delta)
@ -145,7 +146,9 @@ return function()
expect(trace:find("nestedCall")).to.be.ok() expect(trace:find("nestedCall")).to.be.ok()
expect(trace:find("runExecutor")).to.be.ok() expect(trace:find("runExecutor")).to.be.ok()
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() it("should report errors from Promises with _error (< v2)", function()
@ -158,9 +161,29 @@ return function()
local trace = tostring(newPromise._values[1]) local trace = tostring(newPromise._values[1])
expect(trace:find("Sample error")).to.be.ok() 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("...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() expect(trace:find("%[No stack trace available")).to.be.ok()
end) end)
it("should allow callable tables", function()
local promise = Promise.new(setmetatable({}, {
__call = function(_, resolve)
resolve(1)
end,
}))
local called = false
promise:andThen(setmetatable({}, {
__call = function(_, var)
expect(var).to.equal(1)
called = true
end,
}))
expect(called).to.equal(true)
end)
end) end)
describe("Promise.defer", function() describe("Promise.defer", function()
@ -206,7 +229,7 @@ return function()
local promise = Promise.delay(2) local promise = Promise.delay(2)
Promise.delay(1):andThen(function() Promise.delay(1):andThen(function()
promise:cancel() promise:cancel()
end) end)
expect(promise:getStatus()).to.equal(Promise.Status.Started) expect(promise:getStatus()).to.equal(Promise.Status.Started)
@ -308,15 +331,12 @@ return function()
local promise = Promise.resolve(5) local promise = Promise.resolve(5)
local chained = promise:andThen( local chained = promise:andThen(function(...)
function(...) argsLength, args = pack(...)
argsLength, args = pack(...) callCount = callCount + 1
callCount = callCount + 1 end, function()
end, badCallCount = badCallCount + 1
function() end)
badCallCount = badCallCount + 1
end
)
expect(badCallCount).to.equal(0) expect(badCallCount).to.equal(0)
@ -342,15 +362,12 @@ return function()
local promise = Promise.reject(5) local promise = Promise.reject(5)
local chained = promise:andThen( local chained = promise:andThen(function(...)
function(...) badCallCount = badCallCount + 1
badCallCount = badCallCount + 1 end, function(...)
end, argsLength, args = pack(...)
function(...) callCount = callCount + 1
argsLength, args = pack(...) end)
callCount = callCount + 1
end
)
expect(badCallCount).to.equal(0) expect(badCallCount).to.equal(0)
@ -397,16 +414,13 @@ return function()
startResolution = resolve startResolution = resolve
end) end)
local chained = promise:andThen( local chained = promise:andThen(function(...)
function(...) args = { ... }
args = {...} argsLength = select("#", ...)
argsLength = select("#", ...) callCount = callCount + 1
callCount = callCount + 1 end, function()
end, badCallCount = badCallCount + 1
function() end)
badCallCount = badCallCount + 1
end
)
expect(callCount).to.equal(0) expect(callCount).to.equal(0)
expect(badCallCount).to.equal(0) expect(badCallCount).to.equal(0)
@ -440,16 +454,13 @@ return function()
startResolution = reject startResolution = reject
end) end)
local chained = promise:andThen( local chained = promise:andThen(function()
function() badCallCount = badCallCount + 1
badCallCount = badCallCount + 1 end, function(...)
end, args = { ... }
function(...) argsLength = select("#", ...)
args = {...} callCount = callCount + 1
argsLength = select("#", ...) end)
callCount = callCount + 1
end
)
expect(callCount).to.equal(0) expect(callCount).to.equal(0)
expect(badCallCount).to.equal(0) expect(badCallCount).to.equal(0)
@ -476,9 +487,7 @@ return function()
local x, y, z local x, y, z
Promise.new(function(resolve, reject) Promise.new(function(resolve, reject)
reject(1, 2, 3) reject(1, 2, 3)
end) end):andThen(function() end):catch(function(a, b, c)
:andThen(function() end)
:catch(function(a, b, c)
x, y, z = a, b, c x, y, z = a, b, c
end) end)
@ -492,11 +501,13 @@ return function()
it("should mark promises as cancelled and not resolve or reject them", function() it("should mark promises as cancelled and not resolve or reject them", function()
local callCount = 0 local callCount = 0
local finallyCallCount = 0 local finallyCallCount = 0
local promise = Promise.new(function() end):andThen(function() local promise = Promise.new(function() end)
callCount = callCount + 1 :andThen(function()
end):finally(function() callCount = callCount + 1
finallyCallCount = finallyCallCount + 1 end)
end) :finally(function()
finallyCallCount = finallyCallCount + 1
end)
promise:cancel() promise:cancel()
promise:cancel() -- Twice to check call counts promise:cancel() -- Twice to check call counts
@ -556,7 +567,9 @@ return function()
it("should track consumers", function() it("should track consumers", function()
local pending = Promise.new(function() end) local pending = Promise.new(function() end)
local p0 = Promise.resolve() local p0 = Promise.resolve()
local p1 = p0:finally(function() return pending end) local p1 = p0:finally(function()
return pending
end)
local p2 = Promise.new(function(resolve) local p2 = Promise.new(function(resolve)
resolve(p1) resolve(p1)
end) end)
@ -596,9 +609,7 @@ return function()
end):finally(finally) end):finally(finally)
-- Chained promise -- Chained promise
Promise.resolve():andThen(function() Promise.resolve():andThen(function() end):finally(finally):finally(finally)
end):finally(finally):finally(finally)
-- Rejected promise -- Rejected promise
Promise.reject():finally(finally) Promise.reject():finally(finally)
@ -620,11 +631,13 @@ return function()
it("should forward return values", function() it("should forward return values", function()
local value local value
Promise.resolve():finally(function() Promise.resolve()
return 1 :finally(function()
end):andThen(function(v) return 1
value = v end)
end) :andThen(function(v)
value = v
end)
expect(value).to.equal(1) expect(value).to.equal(1)
end) end)
@ -649,7 +662,7 @@ return function()
it("should error if given non-promise values", function() it("should error if given non-promise values", function()
expect(function() expect(function()
Promise.all({{}, {}, {}}) Promise.all({ {}, {}, {} })
end).to.throw() end).to.throw()
end) end)
@ -662,7 +675,7 @@ return function()
for i = 1, testValuesLength do for i = 1, testValuesLength do
promises[i] = Promise.new(function(resolve) promises[i] = Promise.new(function(resolve)
resolveFunctions[i] = {resolve, testValues[i]} resolveFunctions[i] = { resolve, testValues[i] }
end) end)
end end
@ -698,7 +711,7 @@ return function()
resolveB = resolve resolveB = resolve
end) end)
local combinedPromise = Promise.all({a, b}) local combinedPromise = Promise.all({ a, b })
expect(combinedPromise:getStatus()).to.equal(Promise.Status.Started) expect(combinedPromise:getStatus()).to.equal(Promise.Status.Started)
@ -727,7 +740,7 @@ return function()
resolveB = resolve resolveB = resolve
end) end)
local combinedPromise = Promise.all({a, b}) local combinedPromise = Promise.all({ a, b })
expect(combinedPromise:getStatus()).to.equal(Promise.Status.Started) expect(combinedPromise:getStatus()).to.equal(Promise.Status.Started)
@ -755,7 +768,7 @@ return function()
rejectB = reject rejectB = reject
end) end)
local combinedPromise = Promise.all({a, b}) local combinedPromise = Promise.all({ a, b })
expect(combinedPromise:getStatus()).to.equal(Promise.Status.Started) expect(combinedPromise:getStatus()).to.equal(Promise.Status.Started)
@ -788,7 +801,7 @@ return function()
expect(Promise.all({ expect(Promise.all({
Promise.resolve(), Promise.resolve(),
Promise.reject(), Promise.reject(),
p p,
}):getStatus()).to.equal(Promise.Status.Rejected) }):getStatus()).to.equal(Promise.Status.Rejected)
expect(p:getStatus()).to.equal(Promise.Status.Cancelled) expect(p:getStatus()).to.equal(Promise.Status.Cancelled)
end) end)
@ -800,7 +813,7 @@ return function()
local promises = { local promises = {
Promise.new(function() end), Promise.new(function() end),
Promise.new(function() end), Promise.new(function() end),
p p,
} }
Promise.all(promises):cancel() Promise.all(promises):cancel()
@ -824,7 +837,7 @@ return function()
end) end)
it("should accept promises in the list", function() it("should accept promises in the list", function()
local sum = Promise.fold({Promise.resolve(1), 2, 3}, function(sum, element) local sum = Promise.fold({ Promise.resolve(1), 2, 3 }, function(sum, element)
return sum + element return sum + element
end, 0) end, 0)
expect(Promise.is(sum)).to.equal(true) expect(Promise.is(sum)).to.equal(true)
@ -833,7 +846,7 @@ return function()
end) end)
it("should always return a promise even if the list or reducer don't use them", function() it("should always return a promise even if the list or reducer don't use them", function()
local sum = Promise.fold({1, 2, 3}, function(sum, element, index) local sum = Promise.fold({ 1, 2, 3 }, function(sum, element, index)
if index == 2 then if index == 2 then
return Promise.delay(1):andThenReturn(sum + element) return Promise.delay(1):andThenReturn(sum + element)
else else
@ -849,7 +862,7 @@ return function()
it("should return the first rejected promise", function() it("should return the first rejected promise", function()
local errorMessage = "foo" local errorMessage = "foo"
local sum = Promise.fold({1, 2, 3}, function(sum, element, index) local sum = Promise.fold({ 1, 2, 3 }, function(sum, element, index)
if index == 2 then if index == 2 then
return Promise.reject(errorMessage) return Promise.reject(errorMessage)
else else
@ -864,14 +877,14 @@ return function()
it("should return the first canceled promise", function() it("should return the first canceled promise", function()
local secondPromise local secondPromise
local sum = Promise.fold({1, 2, 3}, function(sum, element, index) local sum = Promise.fold({ 1, 2, 3 }, function(sum, element, index)
if index == 1 then if index == 1 then
return sum + element return sum + element
elseif index == 2 then elseif index == 2 then
secondPromise = Promise.delay(1):andThenReturn(sum + element) secondPromise = Promise.delay(1):andThenReturn(sum + element)
return secondPromise return secondPromise
else else
error('this should not run if the promise is cancelled') error("this should not run if the promise is cancelled")
end end
end, 0) end, 0)
expect(Promise.is(sum)).to.equal(true) expect(Promise.is(sum)).to.equal(true)
@ -885,7 +898,7 @@ return function()
it("should resolve with the first settled value", function() it("should resolve with the first settled value", function()
local promise = Promise.race({ local promise = Promise.race({
Promise.resolve(1), Promise.resolve(1),
Promise.resolve(2) Promise.resolve(2),
}):andThen(function(value) }):andThen(function(value)
expect(value).to.equal(1) expect(value).to.equal(1)
end) end)
@ -901,7 +914,7 @@ return function()
Promise.new(function() end), Promise.new(function() end),
Promise.new(function(resolve) Promise.new(function(resolve)
resolve(2) resolve(2)
end) end),
} }
local promise = Promise.race(promises) local promise = Promise.race(promises)
@ -916,7 +929,7 @@ return function()
expect(Promise.race({ expect(Promise.race({
Promise.reject(), Promise.reject(),
Promise.resolve(), Promise.resolve(),
p p,
}):getStatus()).to.equal(Promise.Status.Rejected) }):getStatus()).to.equal(Promise.Status.Rejected)
expect(p:getStatus()).to.equal(Promise.Status.Cancelled) expect(p:getStatus()).to.equal(Promise.Status.Cancelled)
end) end)
@ -937,7 +950,7 @@ return function()
local promises = { local promises = {
Promise.new(function() end), Promise.new(function() end),
Promise.new(function() end), Promise.new(function() end),
p p,
} }
Promise.race(promises):cancel() Promise.race(promises):cancel()
@ -965,9 +978,9 @@ return function()
it("should catch errors after a yield", function() it("should catch errors after a yield", function()
local bindable = Instance.new("BindableEvent") local bindable = Instance.new("BindableEvent")
local test = Promise.promisify(function () local test = Promise.promisify(function()
bindable.Event:Wait() bindable.Event:Wait()
error('errortext') error("errortext")
end) end)
local promise = test() local promise = test()
@ -1026,7 +1039,7 @@ return function()
it("should catch synchronous errors", function() it("should catch synchronous errors", function()
local errorText local errorText
Promise.try(function() Promise.try(function()
error('errortext') error("errortext")
end):catch(function(e) end):catch(function(e)
errorText = tostring(e) errorText = tostring(e)
end) end)
@ -1048,7 +1061,7 @@ return function()
local bindable = Instance.new("BindableEvent") local bindable = Instance.new("BindableEvent")
local promise = Promise.try(function() local promise = Promise.try(function()
bindable.Event:Wait() bindable.Event:Wait()
error('errortext') error("errortext")
end) end)
expect(promise:getStatus()).to.equal(Promise.Status.Started) expect(promise:getStatus()).to.equal(Promise.Status.Started)
@ -1127,11 +1140,13 @@ return function()
expect(value).to.equal(true) expect(value).to.equal(true)
local never, always local never, always
Promise.reject():done(function() Promise.reject()
never = true :done(function()
end):finally(function() never = true
always = true end)
end) :finally(function()
always = true
end)
expect(never).to.never.be.ok() expect(never).to.never.be.ok()
expect(always).to.be.ok() expect(always).to.be.ok()
@ -1143,7 +1158,7 @@ return function()
local p = Promise.some({ local p = Promise.some({
Promise.resolve(1), Promise.resolve(1),
Promise.reject(), Promise.reject(),
Promise.resolve(2) Promise.resolve(2),
}, 2) }, 2)
expect(p:getStatus()).to.equal(Promise.Status.Resolved) expect(p:getStatus()).to.equal(Promise.Status.Resolved)
expect(p._values[1][1]).to.equal(1) expect(p._values[1][1]).to.equal(1)
@ -1153,13 +1168,15 @@ return function()
it("should error if the goal can't be reached", function() it("should error if the goal can't be reached", function()
expect(Promise.some({ expect(Promise.some({
Promise.resolve(), Promise.resolve(),
Promise.reject() Promise.reject(),
}, 2):getStatus()).to.equal(Promise.Status.Rejected) }, 2):getStatus()).to.equal(Promise.Status.Rejected)
local reject local reject
local p = Promise.some({ local p = Promise.some({
Promise.resolve(), Promise.resolve(),
Promise.new(function(_, r) reject = r end) Promise.new(function(_, r)
reject = r
end),
}, 2) }, 2)
expect(p:getStatus()).to.equal(Promise.Status.Started) expect(p:getStatus()).to.equal(Promise.Status.Started)
@ -1171,12 +1188,14 @@ return function()
it("should cancel pending Promises once the goal is reached", function() it("should cancel pending Promises once the goal is reached", function()
local resolve local resolve
local pending1 = Promise.new(function() end) local pending1 = Promise.new(function() end)
local pending2 = Promise.new(function(r) resolve = r end) local pending2 = Promise.new(function(r)
resolve = r
end)
local some = Promise.some({ local some = Promise.some({
pending1, pending1,
pending2, pending2,
Promise.resolve() Promise.resolve(),
}, 2) }, 2)
expect(some:getStatus()).to.equal(Promise.Status.Started) expect(some:getStatus()).to.equal(Promise.Status.Started)
@ -1198,7 +1217,7 @@ return function()
it("should return an empty array if amount is 0", function() it("should return an empty array if amount is 0", function()
local p = Promise.some({ local p = Promise.some({
Promise.resolve(2) Promise.resolve(2),
}, 0) }, 0)
expect(p:getStatus()).to.equal(Promise.Status.Resolved) expect(p:getStatus()).to.equal(Promise.Status.Resolved)
@ -1226,7 +1245,7 @@ return function()
local promises = { local promises = {
Promise.new(function() end), Promise.new(function() end),
Promise.new(function() end), Promise.new(function() end),
p p,
} }
Promise.some(promises, 3):cancel() Promise.some(promises, 3):cancel()
@ -1241,7 +1260,7 @@ return function()
local p = Promise.any({ local p = Promise.any({
Promise.reject(), Promise.reject(),
Promise.reject(), Promise.reject(),
Promise.resolve(1) Promise.resolve(1),
}) })
expect(p:getStatus()).to.equal(Promise.Status.Resolved) expect(p:getStatus()).to.equal(Promise.Status.Resolved)
@ -1265,7 +1284,9 @@ return function()
Promise.resolve(), Promise.resolve(),
Promise.reject(), Promise.reject(),
Promise.resolve(), Promise.resolve(),
Promise.new(function(_, r) reject = r end) Promise.new(function(_, r)
reject = r
end),
}) })
expect(p:getStatus()).to.equal(Promise.Status.Started) expect(p:getStatus()).to.equal(Promise.Status.Started)
@ -1284,7 +1305,7 @@ return function()
local promises = { local promises = {
Promise.new(function() end), Promise.new(function() end),
Promise.new(function() end), Promise.new(function() end),
p p,
} }
Promise.allSettled(promises):cancel() Promise.allSettled(promises):cancel()
@ -1349,9 +1370,12 @@ return function()
describe("Promise.each", function() describe("Promise.each", function()
it("should iterate", function() it("should iterate", function()
local ok, result = Promise.each({ local ok, result = Promise.each({
"foo", "bar", "baz", "qux" "foo",
"bar",
"baz",
"qux",
}, function(...) }, function(...)
return {...} return { ... }
end):_unwrap() end):_unwrap()
expect(ok).to.equal(true) expect(ok).to.equal(true)
@ -1370,7 +1394,9 @@ return function()
local callCounts = {} local callCounts = {}
local promise = Promise.each({ local promise = Promise.each({
"foo", "bar", "baz" "foo",
"bar",
"baz",
}, function(value, index) }, function(value, index)
callCounts[index] = (callCounts[index] or 0) + 1 callCounts[index] = (callCounts[index] or 0) + 1
@ -1415,7 +1441,7 @@ return function()
end) end)
it("should reject with the value if the predicate promise rejects", function() it("should reject with the value if the predicate promise rejects", function()
local promise = Promise.each({1, 2, 3}, function() local promise = Promise.each({ 1, 2, 3 }, function()
return Promise.reject("foobar") return Promise.reject("foobar")
end) end)
@ -1430,7 +1456,7 @@ return function()
end) end)
local promise = Promise.each({ local promise = Promise.each({
innerPromise innerPromise,
}, function(value) }, function(value)
return value * 2 return value * 2
end) end)
@ -1445,7 +1471,7 @@ return function()
it("should reject with the value if a Promise from the list rejects", function() it("should reject with the value if a Promise from the list rejects", function()
local called = false local called = false
local promise = Promise.each({1, 2, Promise.reject("foobar")}, function(value) local promise = Promise.each({ 1, 2, Promise.reject("foobar") }, function(value)
called = true called = true
return "never" return "never"
end) end)
@ -1460,7 +1486,7 @@ return function()
cancelled:cancel() cancelled:cancel()
local called = false local called = false
local promise = Promise.each({1, 2, cancelled}, function() local promise = Promise.each({ 1, 2, cancelled }, function()
called = true called = true
end) end)
@ -1473,13 +1499,13 @@ return function()
local callCounts = {} local callCounts = {}
local promise = Promise.each({ local promise = Promise.each({
"foo", "bar", "baz" "foo",
"bar",
"baz",
}, function(value, index) }, function(value, index)
callCounts[index] = (callCounts[index] or 0) + 1 callCounts[index] = (callCounts[index] or 0) + 1
return Promise.new(function() return Promise.new(function() end)
end)
end) end)
expect(promise:getStatus()).to.equal(Promise.Status.Started) expect(promise:getStatus()).to.equal(Promise.Status.Started)
@ -1497,10 +1523,11 @@ return function()
local innerPromise local innerPromise
local promise = Promise.each({ local promise = Promise.each({
"foo", "bar", "baz" "foo",
"bar",
"baz",
}, function(value, index) }, function(value, index)
innerPromise = Promise.new(function() innerPromise = Promise.new(function() end)
end)
return innerPromise return innerPromise
end) end)
@ -1512,7 +1539,7 @@ return function()
it("should cancel Promises in the list if Promise.each is cancelled", function() it("should cancel Promises in the list if Promise.each is cancelled", function()
local innerPromise = Promise.new(function() end) local innerPromise = Promise.new(function() end)
local promise = Promise.each({innerPromise}, function() end) local promise = Promise.each({ innerPromise }, function() end)
promise:cancel() promise:cancel()
@ -1595,7 +1622,7 @@ return function()
local obj = { local obj = {
andThen = function() andThen = function()
return 1 return 1
end end,
} }
expect(Promise.is(obj)).to.equal(true) expect(Promise.is(obj)).to.equal(true)
@ -1606,9 +1633,7 @@ return function()
OldPromise.prototype = {} OldPromise.prototype = {}
OldPromise.__index = OldPromise.prototype OldPromise.__index = OldPromise.prototype
function OldPromise.prototype:andThen() function OldPromise.prototype:andThen() end
end
local oldPromise = setmetatable({}, OldPromise) local oldPromise = setmetatable({}, OldPromise)