Implement Promise.all (#4)

This commit is contained in:
Eryn Lynn 2018-09-14 14:17:06 -04:00 committed by Lucien Greathouse
parent e6b43a1fae
commit 371f08598c
3 changed files with 111 additions and 4 deletions

View file

@ -25,6 +25,11 @@ This Promise implementation attempts to satisfy those traits.
* Creates an immediately rejected Promise with the given value.
* `Promise.is(object) -> bool`
* Returns whether the given object is a Promise.
* `Promise.all(array) -> array`
* Accepts an array of promises and returns a new promise that:
* is resolved after all input promises resolve.
* is rejected if ANY input promises reject.
* Note: Only the first return value from each promise will be present in the resulting array.
### Instance Methods
* `Promise:andThen(successHandler, [failureHandler]) -> Promise`
@ -69,8 +74,6 @@ httpGet("https://google.com")
```
## Future Additions
* `Promise.all`
* Currently stubbed out, throws an error.
* `Promise.wrapAsync`
* Intended to wrap an existing Roblox API that yields, exposing a new one that returns a Promise.

View file

@ -165,8 +165,60 @@ end
* is resolved when all input promises resolve
* is rejected if ANY input promises reject
]]
function Promise.all(...)
error("unimplemented", 2)
function Promise.all(promises)
if type(promises) ~= "table" then
error("Please pass an array of promises or values to Promise.all", 2)
end
-- If there are no values then return an already resolved promise.
if #promises == 0 then
return Promise.resolve({})
end
-- We need to check that each value is a promise here so that we can produce a
-- proper error rather than a rejected promise with our error.
for i = 1, #promises do
if not Promise.is(promises[i]) then
error(("Non-promise value passed into Promise.all at index #%d"):format(i), 2)
end
end
return Promise.new(function(resolve, reject)
-- An array to contain our resolved values from the given promises.
local resolvedValues = {}
-- Keep a count of resolved promises because just checking the resolved values length
-- wouldn't account for promises that resolve with nil.
local resolvedCount = 0
local rejected = false
-- Called when a single value is resolved and resolves if all are done.
local function resolveOne(i, ...)
if rejected then
-- Bail out if this promise has already been rejected.
return
end
resolvedValues[i] = ...
resolvedCount = resolvedCount + 1
if resolvedCount == #promises then
resolve(resolvedValues)
end
end
-- We can assume the values inside `promises` are all promises since we checked above.
for i = 1, #promises do
promises[i]:andThen(function(...)
resolveOne(i, ...)
end)
:catch(function(...)
-- Only reject if this promise is unrejected.
if not rejected then
reject(...)
end
end)
end
end)
end
--[[

View file

@ -264,4 +264,56 @@ return function()
expect(#chained._values).to.equal(0)
end)
end)
describe("Promise.all", function()
it("should error if given something other than a table", function()
expect(function()
Promise.all(1)
end).to.throw()
end)
it("should resolve instantly with an empty table if given no values", function()
local promise = Promise.all({})
local success, value = promise:await()
expect(success).to.equal(true)
expect(promise:getStatus()).to.equal(Promise.Status.Resolved)
expect(value).to.be.a("table")
expect(#value).to.equal(0)
end)
it("should error if given non-promise values", function()
expect(function()
Promise.all({{}, {}, {}})
end).to.throw()
end)
it("should wait for all promises to be resolved and return their values", function()
local promises = {
Promise.new(function(resolve)
resolve(1)
end),
Promise.new(function(resolve)
resolve("A string")
end),
Promise.new(function(resolve)
resolve(nil)
end),
Promise.new(function(resolve)
resolve(false)
end)
}
local promise = Promise.all(promises)
local success, resolved = promise:await()
expect(success).to.equal(true)
expect(resolved).to.be.a("table")
expect(#resolved).to.equal(4)
expect(resolved[1]).to.equal(1)
expect(resolved[2]).to.equal("A string")
expect(resolved[3]).to.equal(nil)
expect(resolved[4]).to.equal(false)
end)
end)
end