Make all/race cancel correctly

This commit is contained in:
Eryn Lynn 2019-09-28 00:13:30 -04:00
parent 3dbb121906
commit 17fac4d008
5 changed files with 48 additions and 18 deletions

View file

@ -1,3 +1,7 @@
# 2.4.1
- Fix issue with Promise.race/all always cancelling instead of only cancelling if the Promise has no other consumers
# 2.4.0 # 2.4.0
- `Promise.is` now only checks if the object is "andThennable" (has an `andThen` method). - `Promise.is` now only checks if the object is "andThennable" (has an `andThen` method).

View file

@ -174,6 +174,8 @@ docs:
* is resolved after all input promises resolve. * is resolved after all input promises resolve.
* is rejected if ANY input promises reject. * is rejected if ANY input promises reject.
Note: Only the first return value from each promise will be present in the resulting array. Note: Only the first return value from each promise will be present in the resulting array.
After any input Promise rejects, all other input Promises that are still pending will be cancelled if they have no other consumers.
static: true static: true
params: "promises: array<Promise<T>>" params: "promises: array<Promise<T>>"
returns: Promise<array<T>> returns: Promise<array<T>>
@ -181,7 +183,7 @@ docs:
desc: | desc: |
Accepts an array of Promises and returns a new promise that is resolved or rejected as soon as any Promise in the array resolves or rejects. Accepts an array of Promises and returns a new promise that is resolved or rejected as soon as any Promise in the array resolves or rejects.
All other Promises that don't win the race will be cancelled. All other Promises that don't win the race will be cancelled if they have no other consumers.
static: true static: true
params: "promises: array<Promise<T>>" params: "promises: array<Promise<T>>"
returns: Promise<T> returns: Promise<T>

View file

@ -227,10 +227,12 @@ function Promise.all(promises)
return Promise.new(function(resolve, reject) return Promise.new(function(resolve, reject)
-- An array to contain our resolved values from the given promises. -- An array to contain our resolved values from the given promises.
local resolvedValues = {} local resolvedValues = {}
local newPromises = {}
-- Keep a count of resolved promises because just checking the resolved -- Keep a count of resolved promises because just checking the resolved
-- values length wouldn't account for promises that resolve with nil. -- values length wouldn't account for promises that resolve with nil.
local resolvedCount = 0 local resolvedCount = 0
local finalized = false
-- Called when a single value is resolved and resolves if all are done. -- Called when a single value is resolved and resolves if all are done.
local function resolveOne(i, ...) local function resolveOne(i, ...)
@ -245,13 +247,25 @@ function Promise.all(promises)
-- We can assume the values inside `promises` are all promises since we -- We can assume the values inside `promises` are all promises since we
-- checked above. -- checked above.
for i = 1, #promises do for i = 1, #promises do
promises[i]:andThen( if finalized then
function(...) break
resolveOne(i, ...) end
end,
function(...) table.insert(
reject(...) newPromises,
end promises[i]:andThen(
function(...)
resolveOne(i, ...)
end,
function(...)
for _, promise in ipairs(newPromises) do
promise:cancel()
end
finalized = true
reject(...)
end
)
) )
end end
end) end)
@ -269,9 +283,11 @@ function Promise.race(promises)
end end
return Promise.new(function(resolve, reject, onCancel) return Promise.new(function(resolve, reject, onCancel)
local newPromises = {}
local function finalize(callback) local function finalize(callback)
return function (...) return function (...)
for _, promise in ipairs(promises) do for _, promise in ipairs(newPromises) do
promise:cancel() promise:cancel()
end end
@ -279,10 +295,15 @@ function Promise.race(promises)
end end
end end
onCancel(finalize(reject)) if onCancel(finalize(reject)) then
return
end
for _, promise in ipairs(promises) do for _, promise in ipairs(promises) do
promise:andThen(finalize(resolve), finalize(reject)) table.insert(
newPromises,
promise:andThen(finalize(resolve), finalize(reject))
)
end end
end) end)
end end

View file

@ -480,8 +480,8 @@ return function()
expect(combinedPromise:getStatus()).to.equal(Promise.Status.Started) expect(combinedPromise:getStatus()).to.equal(Promise.Status.Started)
resolveB("foo", "bar")
rejectA("baz", "qux") rejectA("baz", "qux")
resolveB("foo", "bar")
local resultLength, result = pack(combinedPromise:_unwrap()) local resultLength, result = pack(combinedPromise:_unwrap())
local success, first, second = unpack(result, 1, resultLength) local success, first, second = unpack(result, 1, resultLength)
@ -490,6 +490,7 @@ return function()
expect(success).to.equal(false) expect(success).to.equal(false)
expect(first).to.equal("baz") expect(first).to.equal("baz")
expect(second).to.equal("qux") expect(second).to.equal("qux")
expect(b:getStatus()).to.equal(Promise.Status.Cancelled)
end) end)
it("should not resolve if resolved after rejecting", function() it("should not resolve if resolved after rejecting", function()
@ -574,10 +575,11 @@ return function()
end) end)
it("should cancel other promises", function() it("should cancel other promises", function()
local promise = Promise.new(function() end)
promise:andThen(function() end)
local promises = { local promises = {
Promise.new(function(resolve) promise,
-- resolve(1) Promise.new(function() end),
end),
Promise.new(function(resolve) Promise.new(function(resolve)
resolve(2) resolve(2)
end) end)
@ -587,8 +589,9 @@ return function()
expect(promise:getStatus()).to.equal(Promise.Status.Resolved) expect(promise:getStatus()).to.equal(Promise.Status.Resolved)
expect(promise._values[1]).to.equal(2) expect(promise._values[1]).to.equal(2)
expect(promises[1]:getStatus()).to.equal(Promise.Status.Cancelled) expect(promises[1]:getStatus()).to.equal(Promise.Status.Started)
expect(promises[2]:getStatus()).to.equal(Promise.Status.Resolved) expect(promises[2]:getStatus()).to.equal(Promise.Status.Cancelled)
expect(promises[3]:getStatus()).to.equal(Promise.Status.Resolved)
end) end)
it("should error if a non-array table is passed in", function() it("should error if a non-array table is passed in", function()

View file

@ -1,6 +1,6 @@
[package] [package]
name = "roblox-lua-promise" name = "roblox-lua-promise"
version = "2.4.0" version = "2.4.1"
author = "evaera" author = "evaera"
content_root = "lib" content_root = "lib"