diff --git a/src/source/promises.bs b/src/source/promises.bs index c0ab356..83497e8 100644 --- a/src/source/promises.bs +++ b/src/source/promises.bs @@ -38,7 +38,9 @@ namespace promises return promises.internal.on("finally", promise, callback, context) end function - ' Allows multiple promise operations to be resolved as a single promise. + ' Takes an array of promises as input and returns a single Promise. + ' This returned promise fulfills when all of the input's promises fulfill (including when an empty array is passed), with an array of the fulfillment values. + ' It rejects when any of the input's promises rejects, with this first rejection reason. function all(promiseArray as dynamic) as dynamic ' Create a deferred to be resolved later deferred = promises.create() @@ -109,13 +111,254 @@ namespace promises promises.resolve(promiseArray, deferred) else ' Reject if the supplied list is not an array - promises.reject("Promises.all: did not supply 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 + ' Takes an array of promises as input and returns a single Promise. + ' This returned promise fulfills when all of the input's promises settle (including when an empty array is passed), + ' with an array of objects that describe the outcome of each promise. + function allSettled(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 + results: [] + resolvedCount: 0 + total: promiseArray.count() + done: false + } + + for i = 0 to promiseArray.count() - 1 + promise = promiseArray[i] + if promises.isPromise(promise) then + + ' Watch for both resolved or rejected promises + promises.onThen(promise, sub(result as dynamic, context as dynamic) + + ' Do not process any promises that come in late + ' This can happen if any of the other promises reject + if not context.state.done then + ' Always assign the result to the origin index so results are in the same + ' order as the supplied promiseArray + context.state.results[context.index] = { status: promises.PromiseState.resolved, value: result } + context.state.resolvedCount++ + + if context.state.resolvedCount = context.state.total then + ' All the promises are resolved. + ' Resolve the deferred and make the state as complete + context.state.done = true + promises.resolve(context.state.results, context.state.deferred) + end if + end if + end sub, { state: state, index: i }) + + promises.onCatch(promise, sub(error as dynamic, context as dynamic) + ' Do not process any promises that come in late + ' This can happen if any of the other promises reject + if not context.state.done then + ' Always assign the result to the origin index so results are in the same + ' order as the supplied promiseArray + context.state.results[context.index] = { status: promises.PromiseState.rejected, reason: error } + context.state.resolvedCount++ + + if context.state.resolvedCount = context.state.total then + ' All the promises are resolved. + ' Resolve the deferred and make the state as complete + context.state.done = true + promises.resolve(context.state.results, context.state.deferred) + end if + end if + end sub, { state: state, index: i }) + else + ' The value in the promise array is not a promise. + ' Immediately set the result. + state.results[i] = { status: promises.PromiseState.resolved, value: promise } + state.resolvedCount++ + + if state.resolvedCount = state.total then + ' All the promises are resolved. + ' Resolve the deferred and make the state as complete + state.done = true + promises.resolve(state.results, 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 + + ' Takes an array of promises as input and returns a single Promise. + ' This returned promise fulfills when any of the input's promises fulfills, with this first fulfillment value. + ' It rejects when all of the input's promises reject (including when an empty array is passed), with an AggregateError containing an array of rejection reasons. + function any(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 + errors: [] + resolvedCount: 0 + total: promiseArray.count() + 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 + ' 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, context as dynamic) + ' Do not process any promises that come in late + ' This can happen if any of the other promises reject + if not context.state.done then + ' Always assign the result to the origin index so results are in the same + ' order as the supplied promiseArray + context.state.errors[context.index] = error + context.state.resolvedCount++ + + if context.state.resolvedCount = context.state.total then + ' All the promises are resolved. + ' Resolve the deferred and make the state as complete + context.state.done = true + try + throw { message: "All promises were rejected", errors: context.state.errors } + catch e + promises.reject(e, context.state.deferred) + end try + end if + end if + end sub, { state: state, index: i }) + 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 + ' We can't resolve with a promise if there are no promises to resolve + try + throw { message: "All promises were rejected", errors: [] } + catch e + promises.reject(e, deferred) + end try + end if + + return deferred + end function + + ' Takes an array of promises as input and returns a single Promise. + ' This returned promise settles with the eventual state of the first promise that settles. + 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 + ' We can't resolve with a promise if there are no promises to resolve + try + throw { message: "All promises were rejected", errors: [] } + catch e + promises.reject(e, deferred) + end try + 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() @@ -128,7 +371,7 @@ namespace promises else promise.update({ promiseResult: result }, true) end if - promise.promiseState = promises.internal.PromiseState.resolved + promise.promiseState = promises.PromiseState.resolved end if return promise end function @@ -145,13 +388,13 @@ namespace promises else promise.update({ promiseResult: error }, true) end if - promise.promiseState = promises.internal.PromiseState.rejected + promise.promiseState = promises.PromiseState.rejected end if return promise end function function isComplete(promise as object) as boolean - return promises.isPromise(promise) and (promise.promiseState = promises.internal.PromiseState.resolved or promise.promiseState = promises.internal.PromiseState.rejected) + return promises.isPromise(promise) and (promise.promiseState = promises.PromiseState.resolved or promise.promiseState = promises.PromiseState.rejected) end function ' Determines if the given item is a promise. @@ -224,6 +467,18 @@ namespace promises if promises.isPromise(value) then return value return promises.resolve(value) end function + + enum PromiseState + pending = "pending" + resolved = "resolved" + rejected = "rejected" + end enum + + interface AggregateError + message as string + ' array of dynamic rejected values + errors as dynamic + end interface end namespace namespace promises.internal @@ -285,7 +540,7 @@ namespace promises.internal promiseState = promise.promiseState 'trigger a change if the promise is already resolved - if promiseState = promises.internal.PromiseState.resolved or promiseState = promises.internal.PromiseState.rejected then + if promiseState = promises.PromiseState.resolved or promiseState = promises.PromiseState.rejected then promises.internal.delay(sub (details as object) details.promise.promiseState = details.promiseState end sub, { promise: promise, promiseState: promiseState }) @@ -328,12 +583,12 @@ namespace promises.internal 'handle .then() listeners for each listener in promiseStorage.thenListeners - promises.internal.processPromiseListener(originalPromise, listener, promiseState = promises.internal.PromiseState.resolved, promiseResult) + promises.internal.processPromiseListener(originalPromise, listener, promiseState = promises.PromiseState.resolved, promiseResult) end for 'handle .catch() listeners for each listener in promiseStorage.catchListeners - promises.internal.processPromiseListener(originalPromise, listener, promiseState = promises.internal.PromiseState.rejected, promiseResult) + promises.internal.processPromiseListener(originalPromise, listener, promiseState = promises.PromiseState.rejected, promiseResult) end for 'handle .finally() listeners @@ -416,7 +671,7 @@ namespace promises.internal end try else 'use the current promise value to pass to the next promise (this is a .catch handler) - if originalPromise.promiseState = promises.internal.PromiseState.rejected then + if originalPromise.promiseState = promises.PromiseState.rejected then callbackResult = promises.reject(promiseValue) else callbackResult = promiseValue @@ -431,13 +686,13 @@ namespace promises.internal promiseState = context.callbackPromise.promiseState promiseResult = context.callbackPromise.promiseResult - if promiseState = promises.internal.PromiseState.resolved then + if promiseState = promises.PromiseState.resolved then 'the callback promise is complete. resolve the newPromise promises.resolve(promiseResult, context.newPromise) return end if - if promiseState = promises.internal.PromiseState.rejected then + if promiseState = promises.PromiseState.rejected then promises.reject(promiseResult, context.newPromise) return end if diff --git a/src/source/promises.spec.bs b/src/source/promises.spec.bs index 7c05ffd..e8f7e0c 100644 --- a/src/source/promises.spec.bs +++ b/src/source/promises.spec.bs @@ -34,13 +34,60 @@ namespace tests m.assertFalse(promises.isComplete(invalid)) end sub + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.chain()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @async @it("promise chain") sub _() - promises.chain(promises.resolve(1)).then(sub(_) - m.testSuite.assertTrue(_ = 1, _.tostr()) - end sub).catch(sub(_) - m.testSuite.assertFalse(true, _.tostr()) + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + } + + promises.chain(promises.resolve(1), context).then(sub(result, context) + context.thenCount++ + m.testSuite.assertEqual(result, 1) + end sub).catch(sub(error, context) + context.catchCount++ + m.testSuite.fail("should not get here") + end sub).finally(sub(context) + context.finallyCount++ + m.testSuite.assertEqual(context, { + thenCount: 1 + catchCount: 0 + finallyCount: 1 + }) + m.testSuite.done() + end sub) + end sub + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.all()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @async + @it("handled non-array") + sub _() + promises.chain(promises.all(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.all([])).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) @@ -63,6 +110,40 @@ namespace tests end sub) end sub + @async + @it("resolving works with non-promise entire all promises") + sub _() + promises.chain(promises.all([ + promises.resolve(1) + 2 + promises.resolve(3) + ])).then(sub(_) + msg = "resolved promise result should be [1,2,3]" + m.testSuite.assertTrue(rooibos.common.eqArray(_, [1, 2, 3]), msg) + end sub).catch(sub(_) + m.testSuite.fail("should not get here") + end sub).finally(sub() + m.testSuite.done() + end sub) + end sub + + @async + @it("resolving works with all non-promise entires all promises") + sub _() + promises.chain(promises.all([ + 1 + 2 + 3 + ])).then(sub(_) + msg = "resolved promise result should be [1,2,3]" + m.testSuite.assertTrue(rooibos.common.eqArray(_, [1, 2, 3]), msg) + end sub).catch(sub(_) + m.testSuite.fail("should not get here") + end sub).finally(sub() + m.testSuite.done() + end sub) + end sub + @async @it("rejecting all promises") sub _() @@ -80,6 +161,518 @@ namespace tests end sub) end sub + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.allSettled()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @async + @it("handled non-array") + sub _() + promises.chain(promises.allSettled(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.allSettled([])).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("resolving all promises in allSettled") + sub _() + promises.chain(promises.allSettled([ + promises.resolve(1) + promises.resolve(2) + promises.resolve(3) + ])).then(sub(_) + msg = "resolved promise result should be [1,2,3]" + m.testSuite.assertTrue(rooibos.common.eqArray(_, [ + { status: promises.PromiseState.resolved, value: 1 }, + { status: promises.PromiseState.resolved, value: 2 }, + { status: promises.PromiseState.resolved, value: 3 } + ]), msg) + end sub).catch(sub(_) + m.testSuite.fail("should not get here") + end sub).finally(sub() + m.testSuite.done() + end sub) + end sub + + @async + @it("resolving works with non-promise entire in allSettled") + sub _() + promises.chain(promises.allSettled([ + promises.resolve(1) + 2 + promises.resolve(3) + ])).then(sub(_) + msg = "resolved promise result should be [1,2,3]" + m.testSuite.assertTrue(rooibos.common.eqArray(_, [ + { status: promises.PromiseState.resolved, value: 1 }, + { status: promises.PromiseState.resolved, value: 2 }, + { status: promises.PromiseState.resolved, value: 3 } + ]), msg) + end sub).catch(sub(_) + m.testSuite.fail("should not get here") + end sub).finally(sub() + m.testSuite.done() + end sub) + end sub + + @async + @it("resolving works with all non-promise entire in allSettled") + sub _() + promises.chain(promises.allSettled([ + 1 + 2 + 3 + ])).then(sub(_) + msg = "resolved promise result should be [1,2,3]" + m.testSuite.assertTrue(rooibos.common.eqArray(_, [ + { status: promises.PromiseState.resolved, value: 1 }, + { status: promises.PromiseState.resolved, value: 2 }, + { status: promises.PromiseState.resolved, value: 3 } + ]), msg) + end sub).catch(sub(_) + m.testSuite.fail("should not get here") + end sub).finally(sub() + m.testSuite.done() + end sub) + end sub + + @async + @it("rejecting a promise in allSettled") + sub _() + promises.chain(promises.allSettled([ + promises.resolve(1) + promises.reject(2) + promises.resolve(3) + ])).then(sub(_) + m.testSuite.assertTrue(rooibos.common.eqArray(_, [ + { status: promises.PromiseState.resolved, value: 1 }, + { status: promises.PromiseState.rejected, reason: 2 }, + { status: promises.PromiseState.resolved, value: 3 } + ])) + end sub).catch(sub(_) + m.testSuite.fail("should not get here") + end sub).finally(sub() + m.testSuite.done() + end sub) + end sub + + @async + @it("rejecting all promises in allSettled") + sub _() + promises.chain(promises.allSettled([ + promises.reject(1) + promises.reject(2) + promises.reject(3) + ])).then(sub(_) + m.testSuite.assertTrue(rooibos.common.eqArray(_, [ + { status: promises.PromiseState.rejected, reason: 1 }, + { status: promises.PromiseState.rejected, reason: 2 }, + { status: promises.PromiseState.rejected, reason: 3 } + ])) + end sub).catch(sub(_) + m.testSuite.fail("should not get here") + end sub).finally(sub() + m.testSuite.done() + end sub) + end sub + + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.any()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @async + @it("handled non-array") + sub _() + promises.chain(promises.any(invalid)).then(sub(result) + m.testSuite.fail("should not get here") + end sub).catch(sub(error) + m.testSuite.assertEqual(error.message, "All promises were rejected") + m.testSuite.assertEqual(error.errors, []) + m.testSuite.assertNotInvalid(error.backtrace) + end sub).finally(sub() + m.testSuite.done() + end sub) + end sub + + @async + @it("handled empty array") + sub _() + promises.chain(promises.any([])).then(sub(result) + m.testSuite.fail("should not get here") + end sub).catch(sub(error) + m.testSuite.assertEqual(error.message, "All promises were rejected") + m.testSuite.assertEqual(error.errors, []) + m.testSuite.assertNotInvalid(error.backtrace) + 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.any(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.any(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.any(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(2) + ] + + promises.chain(promises.any(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) + 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(2) + ] + + promises.chain(promises.any(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(2) + ] + + promises.chain(promises.any(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.any(promiseArray), promiseArray).then(sub(result, promiseArray) + m.testSuite.fail("should not get here") + end sub).catch(sub(error, promiseArray) + m.testSuite.assertEqual(error.message, "All promises were rejected") + m.testSuite.assertEqual(error.errors, ["1", "2", "3"]) + m.testSuite.assertNotInvalid(error.backtrace) + end sub).finally(sub(promiseArray) + m.testSuite.done() + 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.assertEqual(error.message, "All promises were rejected") + m.testSuite.assertEqual(error.errors, []) + m.testSuite.assertNotInvalid(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.fail("should not get here") + end sub).catch(sub(error) + m.testSuite.assertEqual(error.message, "All promises were rejected") + m.testSuite.assertEqual(error.errors, []) + m.testSuite.assertNotInvalid(error.backtrace) + 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()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @async @it("thenable promise") sub _() @@ -89,6 +682,10 @@ namespace tests end sub) end sub + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.onCatch()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @async @it("catchable promise") sub _() @@ -98,6 +695,10 @@ namespace tests end sub) end sub + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.onFinally()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @async @it("finalable promise") sub _() @@ -107,6 +708,10 @@ namespace tests end sub) end sub + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("does not resolve to soon or too late") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @async @it("timer promise") function _() @@ -126,6 +731,10 @@ namespace tests }) end function + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.resolve()/promises.reject()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @async @it("resolved invalid") function _() @@ -229,9 +838,9 @@ namespace tests @async @it("resolved array") function _() - promise = promises.resolve([1,2,3]) + promise = promises.resolve([1, 2, 3]) promises.onThen(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, [1,2,3]) + m.testSuite.assertEqual(result, [1, 2, 3]) m.testSuite.done() end sub) end function @@ -239,9 +848,9 @@ namespace tests @async @it("reject array") function _() - promise = promises.reject([1,2,3]) + promise = promises.reject([1, 2, 3]) promises.onCatch(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, [1,2,3]) + m.testSuite.assertEqual(result, [1, 2, 3]) m.testSuite.done() end sub) end function @@ -399,3 +1008,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