Skip to content

Commit

Permalink
Added promises.race
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisdp committed Nov 8, 2024
1 parent 57790b6 commit a138fb6
Show file tree
Hide file tree
Showing 2 changed files with 286 additions and 0 deletions.
72 changes: 72 additions & 0 deletions src/source/promises.bs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,78 @@ namespace promises
return deferred
end function

' Allows multiple promise operations to be resolved as a single promise.
function race(promiseArray as dynamic) as dynamic
' Create a deferred to be resolved later
deferred = promises.create()

if type(promiseArray) = "roArray" and not promiseArray.isEmpty() then
' Track the state and results of all the promises
state = {
deferred: deferred
done: false
}

for i = 0 to promiseArray.count() - 1
promise = promiseArray[i]
if promises.isPromise(promise) then

if promise.promiseState = promises.PromiseState.resolved then
' Do not process any promises that come in after the first resolved one
if not state.done then
state.done = true
promises.resolve(promise.promiseResult, state.deferred)
end if
else if promise.promiseState = promises.PromiseState.rejected then
' Do not process any promises that come in after the first resolved one
if not state.done then
state.done = true
promises.reject(promise.promiseResult, state.deferred)
end if
else
' Watch for both resolved or rejected promises
promises.onThen(promise, sub(result as dynamic, state as dynamic)
' Do not process any promises that come in after the first resolved one
if not state.done then
state.done = true
promises.resolve(result, state.deferred)
end if
end sub, state)

promises.onCatch(promise, sub(error as dynamic, state as dynamic)
' Do not process any promises that come in after the first resolved one
if not state.done then
state.done = true
promises.reject(error, state.deferred)
end if
end sub, state)
end if
else
' The value in the promise array is not a promise.
' Immediately set the result.
if not state.done then
state.done = true
promises.resolve(promise, state.deferred)
end if
end if
end for
else
if type(promiseArray) = "roArray" then
' Resolve when the array is empty
promises.resolve(promiseArray, deferred)
else
' Reject if the supplied list is not an array
try
throw "did not supply an array"
catch e
promises.reject(e, deferred)
end try
end if
end if

return deferred
end function

function resolve(result as dynamic, promise = invalid as dynamic) as object
if not promises.isPromise(promise) then
promise = promises.create()
Expand Down
214 changes: 214 additions & 0 deletions src/source/promises.spec.bs
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,8 @@ namespace tests
end sub).catch(sub(_, promiseArray)
m.testSuite.fail("should not get here")
end sub).finally(sub(promiseArray)
promises.resolve(invalid, promiseArray[0])
promises.resolve(invalid, promiseArray[2])
m.testSuite.done()
end sub)
end sub
Expand Down Expand Up @@ -460,6 +462,206 @@ namespace tests
end sub)
end sub

'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@describe("promises.race()")
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

@async
@it("handled non-array")
sub _()
promises.chain(promises.race(invalid)).then(sub(result)
m.testSuite.fail("should not get here")
end sub).catch(sub(error)
m.testSuite.assetEqual(error.message, "did not supply an array")
m.testSuite.assetNotInvalid(error.backtrace)
end sub).finally(sub()
m.testSuite.done()
end sub)
end sub

@async
@it("handled empty array")
sub _()
promises.chain(promises.race([])).then(sub(result)
m.testSuite.assertEqual(result, [])
end sub).catch(sub(error)
m.testSuite.fail("should not get here")
end sub).finally(sub()
m.testSuite.done()
end sub)
end sub

@async
@it("handled a promise that resolves")
sub _()
promiseArray = [
promises.create()
promises.create()
promises.create()
]

promises.chain(promises.race(promiseArray), promiseArray).then(sub(result, promiseArray)
m.testSuite.assertEqual(result, 2)
end sub).catch(sub(_, promiseArray)
m.testSuite.fail("should not get here")
end sub).finally(sub(promiseArray)
promises.resolve(invalid, promiseArray[0])
promises.resolve(invalid, promiseArray[2])

m.testSuite.done()
end sub)

promises.resolve(2, promiseArray[1])
end sub

@async
@it("handles a pre-resolved promise")
sub _()
promiseArray = [
promises.create()
promises.resolve(2)
promises.create()
]

promises.chain(promises.race(promiseArray), promiseArray).then(sub(result, promiseArray)
m.testSuite.assertEqual(result, 2)
end sub).catch(sub(_, promiseArray)
m.testSuite.fail("should not get here")
end sub).finally(sub(promiseArray)
promises.resolve(invalid, promiseArray[0])
promises.resolve(invalid, promiseArray[2])

m.testSuite.done()
end sub)
end sub

@async
@it("handles a non-promise value amongst pending promises")
sub _()
promiseArray = [
promises.create()
2
promises.create()
]

promises.chain(promises.race(promiseArray), promiseArray).then(sub(result, promiseArray)
m.testSuite.assertEqual(result, 2)
end sub).catch(sub(_, promiseArray)
m.testSuite.fail("should not get here")
end sub).finally(sub(promiseArray)
promises.resolve(invalid, promiseArray[0])
promises.resolve(invalid, promiseArray[2])
m.testSuite.done()
end sub)
end sub

@async
@it("handles a non-promise value amongst rejected promises")
sub _()
promiseArray = [
promises.reject(1)
2
promises.reject(3)
]

promises.chain(promises.race(promiseArray), promiseArray).then(sub(result, promiseArray)
m.testSuite.fail("should not get here")
end sub).catch(sub(error, promiseArray)
m.testSuite.assertEqual(error, 1)
end sub).finally(sub(promiseArray)
m.testSuite.done()
end sub)
end sub

@async
@it("handles a first pre-resolved promise")
sub _()
promiseArray = [
promises.resolve(1)
promises.resolve(2)
promises.resolve(3)
]

promises.chain(promises.race(promiseArray), promiseArray).then(sub(result, promiseArray)
m.testSuite.assertEqual(result, 1)
end sub).catch(sub(_, promiseArray)
m.testSuite.fail("should not get here")
end sub).finally(sub(promiseArray)
m.testSuite.done()
end sub)
end sub

@async
@it("handles first pre-resolved promise along with a non-promise value")
sub _()
promiseArray = [
promises.resolve(1)
2
promises.resolve(3)
]

promises.chain(promises.race(promiseArray), promiseArray).then(sub(result, promiseArray)
m.testSuite.assertEqual(result, 1)
end sub).catch(sub(_, promiseArray)
m.testSuite.fail("should not get here")
end sub).finally(sub(promiseArray)
m.testSuite.done()
end sub)
end sub

@async
@it("handles all promises being pre-rejected")
sub _()
promiseArray = [
promises.reject("1")
promises.reject("2")
promises.reject("3")
]

promises.chain(promises.race(promiseArray), promiseArray).then(sub(result, promiseArray)
m.testSuite.fail("should not get here")
end sub).catch(sub(error, promiseArray)
m.testSuite.assertEqual(error, "1")
end sub).finally(sub(promiseArray)
m.testSuite.done()
end sub)
end sub

@async
@it("handled a the first promise to resolve")
sub _()
promiseArray = [
toPromiseWithDelay(0.3, 1)
toPromiseWithDelay(0.2, 2)
toPromiseWithDelay(0.1, 3)
]

promises.chain(promises.race(promiseArray), promiseArray).then(sub(result, promiseArray)
m.testSuite.assertEqual(result, 3)
end sub).catch(sub(_, promiseArray)
m.testSuite.fail("should not get here")
end sub).finally(sub(promiseArray)
m.testSuite.done()
end sub)
end sub

@async
@it("handled a the first promise to reject")
sub _()
promiseArray = [
toPromiseWithDelay(0.2, 1)
toPromiseWithDelay(0.1, 2, false)
toPromiseWithDelay(0.3, 3)
]

promises.chain(promises.race(promiseArray), promiseArray).then(sub(result, promiseArray)
m.testSuite.fail("should not get here")
end sub).catch(sub(error, promiseArray)
m.testSuite.assertEqual(error, 2)
end sub).finally(sub(promiseArray)
m.testSuite.done()
end sub)
end sub

'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@describe("promises.onThen()")
Expand Down Expand Up @@ -800,3 +1002,15 @@ function sleepPromise(duration = 0.0001 as float) as dynamic
end sub, promise, duration)
return promise
end function

function toPromiseWithDelay(duration = 0.0001 as float, value = true as dynamic, resolve = true as boolean) as dynamic
differed = promises.create()
promises.internal.delay(sub(context as dynamic)
if context.resolve then
promises.resolve(context.value, context.differed)
else
promises.reject(context.value, context.differed)
end if
end sub, {differed: differed, value: value, resolve: resolve}, duration)
return differed
end function

0 comments on commit a138fb6

Please sign in to comment.