Skip to content

Commit

Permalink
change Promise.all() and Promise.race() behavior act like morden js a…
Browse files Browse the repository at this point in the history
…pi, resolves issue Billiam#7
  • Loading branch information
fengbo committed Dec 24, 2020
1 parent ef9d331 commit 89817b2
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 57 deletions.
65 changes: 40 additions & 25 deletions promise.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
-- Port of https://github.com/rhysbrettbowen/promise_impl/blob/master/promise.js
-- and https://github.com/rhysbrettbowen/Aplus
--
local pack = table.pack or _G.pack

local queue = {}

local State = {
Expand Down Expand Up @@ -211,11 +213,12 @@ function Promise.update()
end

-- resolve when all promises complete
-- see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
function Promise.all(...)
local promises = {...}
local promises = pack(...)
local results = {}
local state = State.FULFILLED
local remaining = #promises
local remaining = promises.n

local promise = Promise.new()

Expand All @@ -226,42 +229,54 @@ function Promise.all(...)
transition(promise, state, results)
end

for i,p in ipairs(promises) do
p:next(
function(value)
results[i] = value
remaining = remaining - 1
check_finished()
end,
function(value)
results[i] = value
remaining = remaining - 1
state = State.REJECTED
check_finished()
end
)
for i = 1, promises.n do
local p = promises[i]
if type(p) == "table" and p.is_promise then
p:next(
function(value)
results[i] = value
remaining = remaining - 1
check_finished()
end,
function(reason)
reject(promise, reason)
end
)
else
results[i] = p
remaining = remaining - 1
end
end

check_finished()

return promise
end

-- resolve with first promise to complete
-- returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects,
-- with the value or reason from that promise
-- see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
function Promise.race(...)
local promises = {...}
local promises = pack(...)
local promise = Promise.new()

Promise.all(...):next(nil, function(value)
reject(promise, value)
end)

local success = function(value)
fulfill(promise, value)
promise:resolve(value)
end

for _,p in ipairs(promises) do
p:next(success)
local fail = function(reason)
promise:reject(reason)
end

for i = 1, promises.n do
local p = promises[i]
if type(p) == "table" and p.is_promise then
p:next(success, fail)
else
-- resolve immediately if a non-promise provided
promise:resolve(p)
break
end
end

return promise
Expand Down
75 changes: 43 additions & 32 deletions spec/extras_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ local dummy = { dummy = 'dummy' } -- we fulfill or reject with this when we don'
local sentinel = { sentinel = 'sentinel' }
local other = { other = 'other' }

-- see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
describe('.all', function()
it("returns results for each promise", function(done)
it("will resolve when all of the input's promises have resolved, or if the input iterable contains no promises", function(done)
async()

local result1 = { result = 1 }
local result2 = { result = 2 }
local result3 = { result = 3 }
local value1 = 42
local value2 = "Hello!"
local value3 = nil

local promise1 = Promise.new()
local promise2 = Promise.new()
Expand All @@ -29,13 +33,13 @@ describe('.all', function()
promise3:resolve(result3)
end)

Promise.all(promise1, promise2, promise3):next(function(results)
assert.are_same({result1, result2, result3}, results)
Promise.all(promise1, promise2, promise3, value1, value2, value3):next(function(results)
assert.are_same({result1, result2, result3, value1, value2, value3}, results)
done()
end)
end)

it("is rejected when any promises are rejected", function(done)
it("is rejected when any promises are rejected, reject with this first rejection message / error", function(done)
async()

local result1 = { result = 1 }
Expand All @@ -54,7 +58,8 @@ describe('.all', function()

Promise.all(promise1, promise2, promise3):next(
nil,
function(results)
function(reason)
assert.are_equals(result2, reason)
done()
end
)
Expand All @@ -69,6 +74,7 @@ describe('.all', function()
end)
end)

-- see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
describe(".race", function()
it("returns the first resolved promise", function(done)
async()
Expand All @@ -95,57 +101,62 @@ describe(".race", function()
end)
end)

it("is resolved when any callbacks are resolved", function(done)
it("fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise", function(done)
async()

local promise1 = Promise.new()
local promise2 = Promise.new()

Helper.timeout(0.1, function()
promise1:reject(other)
promise1:reject(sentinel)
end)

Helper.timeout(0.2, function()
promise2:resolve(sentinel)
promise2:resolve(other)
end)

Promise.race(promise1, promise2):next(function(result)
assert.are_equals(sentinel, result)
local fulfillment = spy.new(function() end)
local rejection = spy.new(function() end)

Promise.race(promise1, promise2):next(function(value)
fulfillment()
end):catch(function(reason)
rejection()
assert.are_equals(sentinel, reason)
end):next(function()
assert.spy(rejection).was_called()
assert.spy(fulfillment).was_not_called()
done()
end)
end)

it("is rejected when all promises are rejected", function(done)
it("immediately resolved when non-promise value provided in list", function(done)
async()

settimeout(0.3)
local result1 = { result = 1 }
local result2 = { result = 2 }
local result3 = { result = 3 }

local promise1 = Promise.new()
local promise2 = Promise.new()
local promise3 = Promise.new()
local value1 = 42

Helper.timeout(0.2, function()
promise1:reject(result1)
Helper.timeout(0.1, function()
promise1:reject(sentinel)
end)

Helper.timeout(0.15, function()
promise2:reject(result2)
Helper.timeout(0.2, function()
promise2:resolve(other)
end)

Helper.timeout(0.1, function()
promise3:reject(result3)
end)
local fulfillment = spy.new(function() end)
local rejection = spy.new(function() end)

Promise.race(promise1, promise2, promise3):next(
function()
end,
function(results)
assert.are_same({result1, result2, result3}, results)
done()
end
)
Promise.race(promise1, promise2, value1):next(function(value)
fulfillment()
assert.are_equals(value, value1)
end):catch(function(reason)
rejection()
end):next(function()
assert.spy(fulfillment).was_called()
assert.spy(rejection).was_not_called()
done()
end)
end)
end)

0 comments on commit 89817b2

Please sign in to comment.