From 6e58731d14c98533e792ba3fa6f953bcf1294da8 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 19 Dec 2025 10:39:14 -0500 Subject: [PATCH 1/2] Better context param mismatch handling --- src/source/promises.bs | 36 +- src/source/promises.spec.bs | 4702 ++++++++++++++++++----------------- 2 files changed, 2426 insertions(+), 2312 deletions(-) diff --git a/src/source/promises.bs b/src/source/promises.bs index e75346b..6c80cd7 100644 --- a/src/source/promises.bs +++ b/src/source/promises.bs @@ -752,7 +752,23 @@ namespace promises.internal end if end try else - callbackResult = callback(promiseValue) + try + lineNumber = LINE_NUM + 1 + callbackResult = callback(promiseValue) + catch error + file = error.backtrace.peek() + if error.number = 241 and file.filename = promises.internal.getLibPath() and file.line_number = lineNumber then + #if PROMISES_SAVE_LISTENER_REGISTRATION_LOCATION + print "[promises.error]: " promises.internal.formatStackTrace(storageItem.registrationLocation, promises.internal.wrongNumberOfParametersMessage) + #else + print "[promises.error]: " promises.internal.formatStackTrace(error, promises.internal.wrongNumberOfParametersMessage) + #end if + callbackResult = callback(promiseValue, 0) ' 0 works for numbers, boolean, object, dynamic. (does not work for string or function, but those are uncommon) + else + promises.internal.logCrashIfEnabled(error) + callbackResult = promises.reject(error) + end if + end try end if '.finally callback takes 1 optional parameter (`context`) @@ -777,7 +793,23 @@ namespace promises.internal end if end try else - callbackResult = callback() + try + lineNumber = LINE_NUM + 1 + callbackResult = callback() + catch error + file = error.backtrace.peek() + if error.number = 241 and file.filename = promises.internal.getLibPath() and file.line_number = lineNumber then + #if PROMISES_SAVE_LISTENER_REGISTRATION_LOCATION + print "[promises.error]: " promises.internal.formatStackTrace(storageItem.registrationLocation, promises.internal.wrongNumberOfParametersMessage) + #else + print "[promises.error]: " promises.internal.formatStackTrace(error, promises.internal.wrongNumberOfParametersMessage) + #end if + callbackResult = callback(0) ' 0 works for numbers, boolean, object, dynamic. (does not work for string or function, but those are uncommon) + else + promises.internal.logCrashIfEnabled(error) + callbackResult = promises.reject(error) + end if + end try end if end if catch e diff --git a/src/source/promises.spec.bs b/src/source/promises.spec.bs index c3f3c84..bfe5726 100644 --- a/src/source/promises.spec.bs +++ b/src/source/promises.spec.bs @@ -1,2323 +1,2405 @@ import "pkg:/source/promises.bs" namespace tests - @SGNode("test") - @suite - class PromisesTests extends rooibos.BaseTestSuite - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("promises.create()") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @it("create promise") - sub _() - promise = promises.create() - m.assertTrue(promises.isPromise(promise)) - end sub - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("promises.isPromise()") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @it("promise validation") - sub _() - m.assertTrue(promises.isPromise(promises.create())) - promiseNode = createNode("node", { promiseState: 0 }) - m.assertTrue(promises.isPromise(promiseNode)) - notPromise = createNode() - m.assertFalse(promises.isPromise(notPromise)) - m.assertFalse(promises.isPromise(invalid)) - end sub - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("promises.isComplete()") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @it("promise settlement check") - sub _() - m.assertFalse(promises.isComplete(promises.create())) - m.assertTrue(promises.isComplete(promises.resolve({}))) - m.assertTrue(promises.isComplete(promises.reject({}))) - m.assertFalse(promises.isComplete(createNode())) - m.assertFalse(promises.isComplete(invalid)) - end sub - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("promises.chain()") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @it("promise chain follows happy path") - function _() - context = { - thenCount: 0 - catchCount: 0 - finallyCount: 0 - result: "" - } - - results = promises.chain(promises.resolve(1), context).then(sub(result, context) - context.thenCount++ - context.result = result - end sub).catch(sub(error, context) - context.catchCount++ - end sub).finally(sub(context) - context.finallyCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 1 - catchCount: 0 - finallyCount: 1 - result: 1 - }) - end sub, context) - end function - - @it("handles default then identify passthrough calls") - function _() - context = { - thenCount: 0 - catchCount: 0 - finallyCount: 0 - result: [] - } - - results = promises.chain(promises.resolve(1), context).then(function(result, context) - context.thenCount++ - context.result.push(result) - return 2 - end function).then().then().then(function(result, context) - context.thenCount++ - context.result.push(result) - end function).catch(sub(error, context) - context.catchCount++ - end sub).finally(sub(context) - context.finallyCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 2 - catchCount: 0 - finallyCount: 1 - result: [1, 2] - }) - end sub, context) - end function - - @it("calls thens in order") - function _() - context = { - thenCount: 0 - catchCount: 0 - finallyCount: 0 - result: [] - } - - results = promises.chain(promises.resolve(1), context).then(function(result, context) - context.thenCount++ - context.result.push(result) - return 2 - end function).then(function(result, context) - context.thenCount++ - context.result.push(result) - return 3 - end function).then(function(result, context) - context.thenCount++ - context.result.push(result) - return 4 - end function).then(function(result, context) - context.thenCount++ - context.result.push(result) - end function).catch(sub(error, context) - context.catchCount++ - end sub).finally(sub(context) - context.finallyCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 4 - catchCount: 0 - finallyCount: 1 - result: [1, 2, 3, 4] - }) - end sub, context) - end function - - @it("calls catches in order") - function _() - context = { - thenCount: 0 - catchCount: 0 - finallyCount: 0 - result: [] - } - - results = promises.chain(promises.reject(1), context).then(function(result, context) - context.thenCount++ - context.result.push(invalid) - end function).catch(function(result, context) - context.catchCount++ - context.result.push(result) - return promises.reject(2) - end function).catch(function(result, context) - context.catchCount++ - context.result.push(result) - return promises.reject(3) - end function).catch(function(result, context) - context.catchCount++ - context.result.push(result) - return promises.reject(4) - end function).catch(function(result, context) - context.catchCount++ - context.result.push(result) - end function).finally(sub(context) - context.finallyCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 0 - catchCount: 4 - finallyCount: 1 - result: [1, 2, 3, 4] - }) - end sub, context) - end function - - @it("handles default then thrower passthrough calls") - function _() - context = { - thenCount: 0 - catchCount: 0 - finallyCount: 0 - result: [] - } - - results = promises.chain(promises.reject(1), context).then(function(result, context) - context.thenCount++ - context.result.push(invalid) - end function).catch(function(result, context) - context.catchCount++ - context.result.push(result) - return promises.reject(2) - end function).catch().catch().catch(function(result, context) - context.catchCount++ - context.result.push(result) - end function).finally(sub(context) - context.finallyCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 0 - catchCount: 2 - finallyCount: 1 - result: [1, 2] - }) - end sub, context) - end function - - @it("calls finally in order") - function _() - context = { - thenCount: 0 - catchCount: 0 - finallyCount: 0 - result: [] - } - - results = promises.chain(promises.resolve(1), context).then(function(result, context) - context.thenCount++ - context.result.push(result) - return 2 - end function).then(function(result, context) - context.thenCount++ - context.result.push(result) - return 3 - end function).then(function(result, context) - context.thenCount++ - context.result.push(result) - return 4 - end function).then(function(result, context) - context.thenCount++ - context.result.push(result) - end function).catch(sub(error, context) - context.catchCount++ - end sub).finally(function(context) - context.finallyCount++ - end function).finally(function(context) - context.finallyCount++ - end function).finally(function(context) - context.finallyCount++ - end function).finally(function(context) - context.finallyCount++ - end function).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 4 - catchCount: 0 - finallyCount: 4 - result: [1, 2, 3, 4] - }) - end sub, context) - end function - - @it("handles default finally passthrough calls") - function _() - context = { - thenCount: 0 - catchCount: 0 - finallyCount: 0 - result: [] - } - - results = promises.chain(promises.resolve(1), context).then(function(result, context) - context.thenCount++ - context.result.push(result) - return 2 - end function).then(function(result, context) - context.thenCount++ - context.result.push(result) - return 3 - end function).then(function(result, context) - context.thenCount++ - context.result.push(result) - return 4 - end function).then(function(result, context) - context.thenCount++ - context.result.push(result) - end function).catch(sub(error, context) - context.catchCount++ - end sub).finally(function(context) - context.finallyCount++ - end function).finally().finally().finally(function(context) - context.finallyCount++ - end function).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 4 - catchCount: 0 - finallyCount: 2 - result: [1, 2, 3, 4] - }) - end sub, context) - end function - - @it("skips thens when promise is rejected") - function _() - context = { - thenCount: 0 - catchCount: 0 - finallyCount: 0 - result: [] - error: "" - } - - results = promises.chain(promises.resolve(1), context).then(function(result, context) - context.thenCount++ - context.result.push(result) - return promises.reject("rejected") - end function).then(function(result, context) - context.thenCount++ - context.result.push(invalid) - end function).then(function(result, context) - context.thenCount++ - context.result.push(invalid) - end function).then(function(result, context) - context.thenCount++ - context.result.push(invalid) - end function).catch(sub(error, context) - context.catchCount++ - context.error = error - end sub).finally(sub(context) - context.finallyCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 1 - catchCount: 1 - finallyCount: 1 - result: [1] - error: "rejected" - }) - end sub, context) - end function - - @it("ignores return value from finally when not crash or rejected promise") - function _() - context = { - thenCount: 0 - catchCount: 0 - finallyCount: 0 - result: "" - } - - results = promises.chain(promises.resolve("I am happy"), context).finally(function(context) - context.finallyCount++ - return "I am sad" - end function).then(sub(result as dynamic, context as dynamic) - context.thenCount++ - context.result = result - end sub).catch(sub(result as dynamic, context as dynamic) - context.catchCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 1 - catchCount: 0 - finallyCount: 1 - result: "I am happy" - }) - end sub, context) - end function - - @it("finally does not prevent rejection from propagating down the chain") - function _() - context = { - thenCount: 0 - catchCount: 0 - finallyCount: 0 - errorMessage: "" - } - - results = promises.chain(promises.reject("Crash"), context).finally(sub(context) - context.finallyCount++ - end sub).then(sub(result as dynamic, context as dynamic) - context.thenCount++ - end sub).catch(sub(result as dynamic, context as dynamic) - context.catchCount++ - context.errorMessage = result - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 0 - catchCount: 1 - finallyCount: 1 - errorMessage: "Crash" - }) - end sub, context) - end function - - @it("finally that returns resolved promises does not prevent rejection from propagating down the chain") - function _() - context = { - thenCount: 0 - catchCount: 0 - finallyCount: 0 - errorMessage: "" - } - - results = promises.chain(promises.reject("Crash"), context).finally(function(context) - context.finallyCount++ - return promises.resolve(true) - end function).then(sub(result as dynamic, context as dynamic) - context.thenCount++ - end sub).catch(sub(result as dynamic, context as dynamic) - context.catchCount++ - context.errorMessage = result - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 0 - catchCount: 1 - finallyCount: 1 - errorMessage: "Crash" - }) - end sub, context) - end function - - @it("finally that returns value does not prevent rejection from propagating down the chain") - function _() - context = { - thenCount: 0 - catchCount: 0 - finallyCount: 0 - errorMessage: "" - } - - results = promises.chain(promises.reject("Crash"), context).finally(function(context) - context.finallyCount++ - return true - end function).then(sub(result as dynamic, context as dynamic) - context.thenCount++ - end sub).catch(sub(result as dynamic, context as dynamic) - context.catchCount++ - context.errorMessage = result - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 0 - catchCount: 1 - finallyCount: 1 - errorMessage: "Crash" - }) - end sub, context) - end function - - @it("returned rejection in finally is propagated down the chain") - function _() - context = { - thenCount: 0 - catchCount: 0 - finallyCount: 0 - errorMessage: "" - } - - results = promises.chain(promises.reject("Crash"), context).finally(function(context) - context.finallyCount++ - return promises.reject("error in finally") - end function).then(sub(result as dynamic, context as dynamic) - context.thenCount++ - end sub).catch(sub(result as dynamic, context as dynamic) - context.catchCount++ - context.errorMessage = result - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 0 - catchCount: 1 - finallyCount: 1 - errorMessage: "error in finally" - }) - end sub, context) - end function - - @it("crash in finally is propagated down the chain") - function _() - context = { - thenCount: 0 - catchCount: 0 - finallyCount: 0 - errorMessage: "" - } - - results = promises.chain(promises.reject("Crash"), context).finally(function(context) - context.finallyCount++ - throw "error in finally" - end function).then(sub(result as dynamic, context as dynamic) - context.thenCount++ - end sub).catch(sub(result as dynamic, context as dynamic) - context.catchCount++ - context.errorMessage = result.message - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 0 - catchCount: 1 - finallyCount: 1 - errorMessage: "error in finally" - }) - end sub, context) - end function - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("promises.all()") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @it("handled non-array") - function _() - context = { - thenCount: 0 - catchCount: 0 - errorMessage: "" - backtrace: invalid - } - - results = promises.chain(promises.all(invalid), context).then(sub(result, context) - context.thenCount++ - end sub).catch(function(error, context) - context.catchCount++ - context.errorMessage = error.message - context.backtrace = error.backtrace - return true - end function).toPromise() - - return promises.onFinally(results, sub(context) - backtrace = context.backtrace - context.delete("backtrace") - m.testSuite.assertEqual(context, { - thenCount: 0 - catchCount: 1 - errorMessage: "Did not supply an array" - }) - m.testSuite.assertNotInvalid(backtrace) - end sub, context) - end function - - @it("handled empty array") - function _() - context = { - thenCount: 0 - catchCount: 0 - result: invalid - } - - results = promises.chain(promises.all([]), context).then(sub(result, context) - context.thenCount++ - context.result = result - end sub).catch(sub(_, context) - context.catchCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 1 - catchCount: 0 - result: [] - }) - end sub, context) - end function - - @it("resolving all promises") - function _() - context = { - thenCount: 0 - catchCount: 0 - result: invalid - } - - results = promises.chain(promises.all([ - promises.resolve(1) - promises.resolve(2) - promises.resolve(3) - ]), context).then(sub(result, context) - context.thenCount++ - context.result = result - end sub).catch(sub(_, context) - context.catchCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 1 - catchCount: 0 - result: [1, 2, 3] - }) - end sub, context) - end function - - @it("resolving works with non-promise entire all promises") - function _() - context = { - thenCount: 0 - catchCount: 0 - result: invalid - } - - results = promises.chain(promises.all([ - promises.resolve(1) - 2 - promises.resolve(3) - ]), context).then(sub(result, context) - context.thenCount++ - context.result = result - end sub).catch(sub(_, context) - context.catchCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 1 - catchCount: 0 - result: [1, 2, 3] - }) - end sub, context) - end function - - @it("resolving works with all non-promise entires all promises") - function _() - context = { - thenCount: 0 - catchCount: 0 - result: invalid - } - - results = promises.chain(promises.all([ - 1 - 2 - 3 - ]), context).then(sub(result, context) - context.thenCount++ - context.result = result - end sub).catch(sub(_, context) - context.catchCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 1 - catchCount: 0 - result: [1, 2, 3] - }) - end sub, context) - end function - - @it("rejecting all promises") - function _() - context = { - thenCount: 0 - catchCount: 0 - result: "" - } - - results = promises.chain(promises.all([ - promises.resolve(1) - promises.reject(2) - promises.resolve(3) - ]), context).then(sub(_, context) - context.thenCount++ - end sub).catch(sub(error, context) - context.catchCount++ - context.result = error - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 0 - catchCount: 1 - result: 2 - }) - end sub, context) - end function - - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("promises.allSettled()") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @it("handled non-array") - function _() - context = { - thenCount: 0 - catchCount: 0 - errorMessage: "" - backtrace: invalid - } - - results = promises.chain(promises.allSettled(invalid), context).then(sub(_, context) - context.thenCount++ - end sub).catch(function(error, context) - context.catchCount++ - context.errorMessage = error.message - context.backtrace = error.backtrace - end function).toPromise() - - return promises.onFinally(results, sub(context) - backtrace = context.backtrace - context.delete("backtrace") - m.testSuite.assertEqual(context, { - thenCount: 0 - catchCount: 1 - errorMessage: "Did not supply an array" - }) - m.testSuite.assertNotInvalid(backtrace) - end sub, context) - end function - - @it("handled empty array") - function _() - context = { - thenCount: 0 - catchCount: 0 - result: invalid - } - - results = promises.chain(promises.allSettled([]), context).then(sub(result, context) - context.thenCount++ - context.result = result - end sub).catch(sub(_, context) - context.catchCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 1 - catchCount: 0 - result: [] - }) - end sub, context) - end function - - @it("resolving all promises in allSettled") - function _() - context = { - thenCount: 0 - catchCount: 0 - result: invalid - } - - results = promises.chain(promises.allSettled([ - promises.resolve(1) - promises.resolve(2) - promises.resolve(3) - ]), context).then(sub(result, context) - context.thenCount++ - context.result = result - end sub).catch(sub(_, context) - context.catchCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 1 - catchCount: 0 - result: [ - { status: promises.PromiseState.resolved, value: 1 } - { status: promises.PromiseState.resolved, value: 2 } - { status: promises.PromiseState.resolved, value: 3 } - ] - }) - end sub, context) - end function - - @it("resolving works with non-promise entire in allSettled") - function _() - context = { - thenCount: 0 - catchCount: 0 - result: invalid - } - - results = promises.chain(promises.allSettled([ - promises.resolve(1) - 2 - promises.resolve(3) - ]), context).then(sub(result, context) - context.thenCount++ - context.result = result - end sub).catch(sub(_, context) - context.catchCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 1 - catchCount: 0 - result: [ - { status: promises.PromiseState.resolved, value: 1 } - { status: promises.PromiseState.resolved, value: 2 } - { status: promises.PromiseState.resolved, value: 3 } - ] - }) - end sub, context) - end function - - @it("resolving works with all non-promise entire in allSettled") - function _() - context = { - thenCount: 0 - catchCount: 0 - result: invalid - } - - results = promises.chain(promises.allSettled([ - 1 - 2 - 3 - ]), context).then(sub(result, context) - context.thenCount++ - context.result = result - end sub).catch(sub(_) - context.catchCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 1 - catchCount: 0 - result: [ - { status: promises.PromiseState.resolved, value: 1 } - { status: promises.PromiseState.resolved, value: 2 } - { status: promises.PromiseState.resolved, value: 3 } - ] - }) - end sub, context) - end function - - @it("rejecting a promise in allSettled") - function _() - context = { - thenCount: 0 - catchCount: 0 - result: invalid - } - - results = promises.chain(promises.allSettled([ - promises.resolve(1) - promises.reject(2) - promises.resolve(3) - ]), context).then(sub(result, context) - context.thenCount++ - context.result = result - end sub).catch(sub(_) - context.catchCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 1 - catchCount: 0 - result: [ - { status: promises.PromiseState.resolved, value: 1 } - { status: promises.PromiseState.rejected, reason: 2 } - { status: promises.PromiseState.resolved, value: 3 } - ] - }) - end sub, context) - end function - - @it("rejecting all promises in allSettled") - function _() - context = { - thenCount: 0 - catchCount: 0 - result: invalid - } - - results = promises.chain(promises.allSettled([ - promises.reject(1) - promises.reject(2) - promises.reject(3) - ]), context).then(sub(result, context) - context.thenCount++ - context.result = result - end sub).catch(sub(_) - context.catchCount++ - end sub).toPromise() - - return promises.onFinally(results, sub(context) - m.testSuite.assertEqual(context, { - thenCount: 1 - catchCount: 0 - result: [ - { status: promises.PromiseState.rejected, reason: 1 } - { status: promises.PromiseState.rejected, reason: 2 } - { status: promises.PromiseState.rejected, reason: 3 } - ] - }) - end sub, context) - end function - - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("promises.any()") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @it("handled non-array") - function _() - context = { - thenCount: 0 - catchCount: 0 - errors: invalid - errorMessage: "" - backtrace: invalid - } - - results = promises.chain(promises.any(invalid), context).then(sub(_, context) - context.thenCount++ - end sub).catch(sub(error, context) - context.catchCount++ - context.errors = error.errors - context.errorMessage = error.message - context.backtrace = error.backtrace - end sub).toPromise() - - return promises.onFinally(results, sub(context) - backtrace = context.backtrace - context.delete("backtrace") - m.testSuite.assertEqual(context, { - thenCount: 0 - catchCount: 1 - errors: [] - errorMessage: "All promises were rejected" - }) - m.testSuite.assertNotInvalid(backtrace) - end sub, context) - end function - - @it("handled empty array") - function _() - context = { - thenCount: 0 - catchCount: 0 - errors: invalid - errorMessage: "" - backtrace: invalid - } - - results = promises.chain(promises.any([]), context).then(sub(_, context) - context.thenCount++ - end sub).catch(sub(error, context) - context.catchCount++ - context.errors = error.errors - context.errorMessage = error.message - context.backtrace = error.backtrace - end sub).toPromise() - - return promises.onFinally(results, sub(context) - backtrace = context.backtrace - context.delete("backtrace") - m.testSuite.assertEqual(context, { - thenCount: 0 - catchCount: 1 - errors: [] - errorMessage: "All promises were rejected" - }) - m.testSuite.assertNotInvalid(backtrace) - end sub, context) - end function - - @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 - - @it("handles a pre-resolved promise") - function _() - promiseArray = [ - promises.create() - promises.resolve(2) - promises.create() - ] - - return 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]) - end sub).toPromise() - end function - - @it("handles a non-promise value amongst pending promises") - function _() - promiseArray = [ - promises.create() - 2 - promises.create() - ] - - return 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]) - end sub).toPromise() - end function - - @it("handles a non-promise value amongst rejected promises") - function _() - promiseArray = [ - promises.reject(1) - 2 - promises.reject(2) - ] - - return 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).toPromise() - end function - - @it("handles a first pre-resolved promise") - function _() - promiseArray = [ - promises.resolve(1) - promises.resolve(2) - promises.resolve(2) - ] - - return 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).toPromise() - end function - - @async - @it("handles first pre-resolved promise along with a non-promise value") - function _() - promiseArray = [ - promises.resolve(1) - 2 - promises.resolve(2) - ] - - return 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).toPromise() - end function - - @it("handles all promises being pre-rejected") - function _() - promiseArray = [ - promises.reject("1") - promises.reject("2") - promises.reject("3") - ] - - return 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).toPromise() - end function - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("promises.race()") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @it("handled non-array") - function _() - return 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).toPromise() - end function - - @it("handled empty array") - function _() - return 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).toPromise() - end function - - @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 - - @it("handles a pre-resolved promise") - function _() - promiseArray = [ - promises.create() - promises.resolve(2) - promises.create() - ] - - return 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]) - end sub).toPromise() - end function - - @it("handles a non-promise value amongst pending promises") - function _() - 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]) - end sub).toPromise() - end function - - @it("handles a non-promise value amongst rejected promises") - function _() - promiseArray = [ - promises.reject(1) - 2 - promises.reject(3) - ] - - return 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).toPromise() - end function - - @it("handles a first pre-resolved promise") - function _() - promiseArray = [ - promises.resolve(1) - promises.resolve(2) - promises.resolve(3) - ] - - return 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).toPromise() - end function - - @it("handles first pre-resolved promise along with a non-promise value") - function _() - promiseArray = [ - promises.resolve(1) - 2 - promises.resolve(3) - ] - - return 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).toPromise() - end function - - @it("handles all promises being pre-rejected") - function _() - promiseArray = [ - promises.reject("1") - promises.reject("2") - promises.reject("3") - ] - - return 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).toPromise() - end function - - @it("handled a the first promise to resolve") - function _() - 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).toPromise() - end function - - @it("handled a the first promise to reject") - function _() - 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).toPromise() - end function - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("promises.onThen()") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @async - @it("catches resolved promises") - sub _() - promises.onThen(promises.resolve("resolved"), sub(value) - m.testSuite.assertEqual(value, "resolved") - m.testSuite.done() - end sub) - end sub - - @async - @it("recovered on wrong num of callback args") - sub _() - context = {} - promises.onThen(promises.resolve("resolved"), sub(value) - m.testSuite.assertEqual(value, "resolved") - m.testSuite.done() - end sub, context) - end sub - - @async - @it("handles empty callbacks") - sub _() - promises.onThen(promises.onThen(promises.resolve("resolved")), sub(value) - m.testSuite.assertEqual(value, "resolved") - m.testSuite.done() - end sub) - end sub - - @async - @it("does not response to rejected promises") - sub _() - context = { thenCount: 0 } - - promise = promises.onThen(promises.reject("rejected"), sub(value, context) - context.thenCount++ - end sub, context) - - promises.onFinally(promise, sub(context) - m.testSuite.assertEqual(context, { thenCount: 0 }) - m.testSuite.done() - end sub, context) - end sub - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("promises.onCatch()") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @async - @it("catches rejected promises") - sub _() - promises.onCatch(promises.reject("rejected"), sub(value) - m.testSuite.assertEqual(value, "rejected") - m.testSuite.done() - end sub) - end sub - - @async - @it("recovered on wrong num of callback args") - sub _() - context = {} - promises.onCatch(promises.reject("rejected"), sub(value) - m.testSuite.assertEqual(value, "rejected") - m.testSuite.done() - end sub, context) - end sub - - @async - @it("handles empty callbacks") - sub _() - promises.onCatch(promises.onCatch(promises.reject("rejected")), sub(value) - m.testSuite.assertEqual(value, "rejected") - m.testSuite.done() - end sub) - end sub - - @async - @it("does not response to resolved promises") - sub _() - context = { catchCount: 0 } - - promise = promises.onCatch(promises.resolve("resolved"), sub(value, context) - context.catchCount++ - end sub, context) - - promises.onFinally(promise, sub(context) - m.testSuite.assertEqual(context, { catchCount: 0 }) - m.testSuite.done() - end sub, context) - end sub - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("promises.onFinally()") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @async - @it("handles resolved promise") - sub _() - promise = promises.onFinally(promises.resolve("resolved"), sub() - end sub) - - promises.onThen(promise, sub(value) - m.testSuite.assertEqual(value, "resolved") - m.testSuite.done() - end sub) - end sub - - @async - @it("recovered on wrong num of callback args") - sub _() - context = {} - promises.onFinally(promises.resolve("resolved"), sub() - m.testSuite.done() - end sub, context) - end sub - - @async - @it("handles resolved promise with empty callback") - sub _() - promise = promises.onFinally(promises.onFinally(promises.resolve("resolved")), sub() - end sub) - - promises.onThen(promise, sub(value) - m.testSuite.assertEqual(value, "resolved") - m.testSuite.done() - end sub) - end sub - - @async - @it("handles rejected promise") - sub _() - promise = promises.onFinally(promises.reject("rejected"), sub() - end sub) - - promises.onCatch(promise, sub(value) - m.testSuite.assertEqual(value, "rejected") - m.testSuite.done() - end sub) - end sub - - @async - @it("handles rejected promise with empty callback") - sub _() - promise = promises.onFinally(promises.onFinally(promises.reject("rejected")), sub() - end sub) - - promises.onCatch(promise, sub(value) - m.testSuite.assertEqual(value, "rejected") - m.testSuite.done() - end sub) - end sub - - @async - @it("handles returning as rejected promise from the finally") - sub _() - promise = promises.onFinally(promises.reject("rejected"), function() - return promises.reject("new rejected promise") - end function) - - promises.onCatch(promise, sub(value) - m.testSuite.assertEqual(value, "new rejected promise") - m.testSuite.done() - end sub) - end sub - - @async - @it("handles rejecting with a crash in the finally") - sub _() - promise = promises.onFinally(promises.reject("rejected"), sub() - throw "new rejected promise" - end sub) - - promises.onCatch(promise, sub(value) - m.testSuite.assertEqual(value.message, "new rejected promise") - m.testSuite.assertNotInvalid(value.backtrace) - m.testSuite.done() - end sub) - end sub - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("promises.enableCrashLogging()") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @beforeEach - sub beforeEachenableCrashLogging() - m.global.removeField("__promises__crashLoggingEnabled") - m.node.delete("formatStackTraceCalled") - end sub - - @afterEach - sub afterEachenableCrashLogging() - m.global.removeField("__promises__crashLoggingEnabled") - m.node.delete("formatStackTraceCalled") - end sub - - @it("properly adds and removes the setting from global") - function _() - m.assertFalse(m.global.hasField("__promises__crashLoggingEnabled")) - promises.configuration.enableCrashLogging(true) - m.assertNodeContainsFields(m.global, { "__promises__crashLoggingEnabled": true }) - promises.configuration.enableCrashLogging(false) - m.assertFalse(m.global.hasField("__promises__crashLoggingEnabled")) - end function - - @it("enableCrashLogging(true) onThen() no context provided should not log") - function _() - promises.configuration.enableCrashLogging(true) - - m.node.formatStackTraceCalled = 0 - m.stubCall(promises.internal.formatStackTrace, function(error, message) - m.formatStackTraceCalled++ - return "" - end function) - - return promises.chain(promises.resolve("resolved")).then(sub(value) - throw "my error" - end sub).catch(sub(error) - m.testSuite.assertEqual(m.formatStackTraceCalled, 0) - end sub).toPromise() - end function - - @it("enableCrashLogging(true) onThen() context was provided") - function _() - promises.configuration.enableCrashLogging(true) - - m.node.formatStackTraceCalled = 0 - m.stubCall(promises.internal.formatStackTrace, function(error, message) - m.testSuite.assertNotInvalid(error.message) - m.testSuite.assertNotInvalid(error.backtrace) - m.testSuite.assertEqual(message, "Divide by Zero.") - m.formatStackTraceCalled++ - return "" - end function) - - return promises.chain(promises.resolve("resolved"), {}).then(sub(value, context) - test = 1/0 - end sub).catch(sub(error, context) - m.testSuite.assertEqual(m.formatStackTraceCalled, 1) - end sub).toPromise() - end function - - @it("enableCrashLogging(true) onCatch() no context provided") - function _() - promises.configuration.enableCrashLogging(true) - - m.node.formatStackTraceCalled = 0 - m.stubCall(promises.internal.formatStackTrace, function(_, _) - m.formatStackTraceCalled++ - return "" - end function) - - return promises.chain(promises.reject("rejected")).catch(sub(error) - test = 1/0 - end sub).catch(sub(error) - m.testSuite.assertEqual(m.formatStackTraceCalled, 1) - end sub).toPromise() - end function - - @it("enableCrashLogging(true) onCatch() context was provided") - function _() - promises.configuration.enableCrashLogging(true) - - m.node.formatStackTraceCalled = 0 - m.stubCall(promises.internal.formatStackTrace, function(_, _) - m.formatStackTraceCalled++ - return "" - end function) - - return promises.chain(promises.reject("rejected"), {}).catch(sub(error, context) - test = 1/0 - end sub).catch(sub(error, context) - m.testSuite.assertEqual(m.formatStackTraceCalled, 1) - end sub).toPromise() - end function - - @it("enableCrashLogging(true) onFinally() no context provided") - function _() - promises.configuration.enableCrashLogging(true) - - m.node.formatStackTraceCalled = 0 - m.stubCall(promises.internal.formatStackTrace, function(_, _) - m.formatStackTraceCalled++ - return "" - end function) - - return promises.chain(promises.resolve("resolved")).finally(sub() - test = 1/0 - end sub).catch(sub(error) - m.testSuite.assertEqual(m.formatStackTraceCalled, 1) - end sub).toPromise() - end function - - @it("enableCrashLogging(true) onFinally() context was provided") - function _() - promises.configuration.enableCrashLogging(true) - - m.node.formatStackTraceCalled = 0 - m.stubCall(promises.internal.formatStackTrace, function(_, _) - m.formatStackTraceCalled++ - return "" - end function) - - return promises.chain(promises.resolve("resolved"), {}).finally(sub(context) - test = 1/0 - end sub).catch(sub(error, context) - m.testSuite.assertEqual(m.formatStackTraceCalled, 1) - end sub).toPromise() - end function - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("does not resolve to soon or too late") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @async - @it("timer promise") - function _() - timerDurationInMillis = 125 - promise = sleepPromise(timerDurationInMillis / 1000) - promises.onThen(promise, sub(_ as dynamic, ctx as dynamic) - elapsedTimeInMillis = ctx.timespan.totalMilliseconds() - ? "elapsed time to resolve promise:" + elapsedTimeInMillis.tostr() - tolerance = ctx.timerDurationInMillis * 0.2 - msg = "did not settle within 10% tolerance of timer duration" - m.testSuite.assertTrue(ctx.timerDurationInMillis - tolerance <= elapsedTimeInMillis, msg) - m.testSuite.assertTrue(ctx.timerDurationInMillis + tolerance >= elapsedTimeInMillis, msg) - m.testSuite.done() - end sub, { - timespan: createObject("roTimespan") - timerDurationInMillis: timerDurationInMillis - }) - end function - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("promises.try()") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @it("returns a the same promise if the callback returns a promise") - function _() - resolvedPromiseParam = promises.resolve(1) - resolvedPromise = promises.try(function(arg) - return arg - end function, [resolvedPromiseParam]) - - m.assertTrue(promises.isPromise(resolvedPromise)) - m.assertTrue(resolvedPromise.isSameNode(resolvedPromiseParam)) - - rejectedPromiseParam = promises.reject("error") - rejectedPromise = promises.try(function(arg) - return arg - end function, [rejectedPromiseParam]) - - m.assertTrue(promises.isPromise(rejectedPromise)) - m.assertTrue(rejectedPromise.isSameNode(rejectedPromiseParam)) - - return promises.onThen(promises.allSettled([resolvedPromise, rejectedPromise]), sub(results) - m.testSuite.assertEqual(results, [ - { status: promises.PromiseState.resolved, value: 1 } - { status: promises.PromiseState.rejected, reason: "error" } - ]) - end sub) - end function - - @it("returns a rejected promise if the callback throws an error") - function _() - promise = promises.try(function() - throw "error" - end function) - - m.assertTrue(promises.isPromise(promise)) - - return promises.onCatch(promise, sub(error) - m.testSuite.assertEqual(error.message, "error") - m.testSuite.assertNotInvalid(error.backTrace) - end sub) - end function - - @it("calls the callback when supplied with no augments wrapped in a promise") - function _() - promise = promises.try(function() - return true - end function) - - m.assertTrue(promises.isPromise(promise)) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, true) - end sub) - end function - - - @it("calls the callback when supplied empty augments") - function _() - promise = promises.try(function() - return true - end function, []) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, true) - end sub) - end function - - @it("calls the callback when supplied with 1 augment") - function _() - promise = promises.try(function(a) - return [a] - end function, [1]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1]) - end sub) - end function - - @it("calls the callback when supplied with 2 augments") - function _() - promise = promises.try(function(a, b) - return [a, b] - end function, [1, 2]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2]) - end sub) - end function - - @it("calls the callback when supplied with 3 augments") - function _() - promise = promises.try(function(a, b, c) - return [a, b, c] - end function, [1, 2, 3]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3]) - end sub) - end function - - @it("calls the callback when supplied with 4 augments") - function _() - promise = promises.try(function(a, b, c, d) - return [a, b, c, d] - end function, [1, 2, 3, 4]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4]) - end sub) - end function - - @it("calls the callback when supplied with 5 augments") - function _() - promise = promises.try(function(a, b, c, d, e) - return [a, b, c, d, e] - end function, [1, 2, 3, 4, 5]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5]) - end sub) - end function - - @it("calls the callback when supplied with 6 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f) - return [a, b, c, d, e, f] - end function, [1, 2, 3, 4, 5, 6]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6]) - end sub) - end function - - @it("calls the callback when supplied with 7 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g) - return [a, b, c, d, e, f, g] - end function, [1, 2, 3, 4, 5, 6, 7]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7]) - end sub) - end function - - @it("calls the callback when supplied with 8 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h) - return [a, b, c, d, e, f, g, h] - end function, [1, 2, 3, 4, 5, 6, 7, 8]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8]) - end sub) - end function - - @it("calls the callback when supplied with 9 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i) - return [a, b, c, d, e, f, g, h, i] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9]) - end sub) - end function - - @it("calls the callback when supplied with 10 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j) - return [a, b, c, d, e, f, g, h, i, j] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - end sub) - end function - - @it("calls the callback when supplied with 11 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k) - return [a, b, c, d, e, f, g, h, i, j, k] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) - end sub) - end function - - @it("calls the callback when supplied with 12 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l) - return [a, b, c, d, e, f, g, h, i, j, k, l] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) - end sub) - end function - - @it("calls the callback when supplied with 13 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) - end sub) - end function - - @it("calls the callback when supplied with 14 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]) - end sub) - end function - - @it("calls the callback when supplied with 15 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) - end sub) - end function - - @it("calls the callback when supplied with 16 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]) - end sub) - end function - - @it("calls the callback when supplied with 17 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]) - end sub) - end function - - @it("calls the callback when supplied with 18 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]) - end sub) - end function - - @it("calls the callback when supplied with 19 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) - end sub) - end function - - @it("calls the callback when supplied with 20 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) - end sub) - end function - - @it("calls the callback when supplied with 21 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) - end sub) - end function - - @it("calls the callback when supplied with 22 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]) - end sub) - end function - - @it("calls the callback when supplied with 23 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]) - end sub) - end function - - @it("calls the callback when supplied with 24 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]) - end sub) - end function - - @it("calls the callback when supplied with 25 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]) - end sub) - end function - - @it("calls the callback when supplied with 26 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]) - end sub) - end function - - @it("calls the callback when supplied with 27 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]) - end sub) - end function - - @it("calls the callback when supplied with 28 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]) - end sub) - end function - - @it("calls the callback when supplied with 29 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]) - end sub) - end function - - @it("calls the callback when supplied with 30 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]) - end sub) - end function - - @it("calls the callback when supplied with 31 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad, ae) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad, ae] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]) - end sub) - end function - - @it("calls the callback when supplied with 32 augments") - function _() - promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad, ae, af) - return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad, ae, af] - end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]) - - return promises.onThen(promise, sub(result) - m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]) - end sub) - end function - - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("promises.resolve()/promises.reject()") - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - @async - @it("resolved invalid") - function _() - promise = promises.resolve(invalid) - promises.onThen(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, invalid) - m.testSuite.done() - end sub) - end function - - @async - @it("reject invalid") - function _() - promise = promises.reject(invalid) - promises.onCatch(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, invalid) - m.testSuite.done() - end sub) - end function - - @async - @it("resolved integer") - function _() - promise = promises.resolve(1) - promises.onThen(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, 1) - m.testSuite.done() - end sub) - end function - - @async - @it("reject integer") - function _() - promise = promises.reject(1) - promises.onCatch(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, 1) - m.testSuite.done() - end sub) - end function - - @async - @it("resolved float") - function _() - promise = promises.resolve(1.1) - promises.onThen(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, 1.1) - m.testSuite.done() - end sub) - end function - - @async - @it("reject float") - function _() - promise = promises.reject(1.1) - promises.onCatch(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, 1.1) - m.testSuite.done() - end sub) - end function - - @async - @it("resolved boolean") - function _() - promise = promises.resolve(true) - promises.onThen(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, true) - m.testSuite.done() - end sub) - end function - - @async - @it("reject boolean") - function _() - promise = promises.reject(true) - promises.onCatch(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, true) - m.testSuite.done() - end sub) - end function - - @async - @it("resolved string") - function _() - promise = promises.resolve("my string") - promises.onThen(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, "my string") - m.testSuite.done() - end sub) - end function - - @async - @it("reject string") - function _() - promise = promises.reject("my string") - promises.onCatch(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, "my string") - m.testSuite.done() - end sub) - end function - - @async - @it("resolved array") - function _() - promise = promises.resolve([1, 2, 3]) - promises.onThen(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, [1, 2, 3]) - m.testSuite.done() - end sub) - end function - - @async - @it("reject array") - function _() - promise = promises.reject([1, 2, 3]) - promises.onCatch(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, [1, 2, 3]) - m.testSuite.done() - end sub) - end function - - @async - @it("resolved AA") - function _() - promise = promises.resolve({ - key: "value" - }) - promises.onThen(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, { - key: "value" - }) - m.testSuite.done() - end sub) - end function - - @async - @it("reject AA") - function _() - promise = promises.reject({ - key: "value" - }) - promises.onCatch(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, { - key: "value" - }) - m.testSuite.done() - end sub) - end function - - @async - @it("resolved AA with subtype") - function _() - promise = promises.resolve({ - subType: "Node" - }) - promises.onThen(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, { - subType: "Node" - }) - m.testSuite.done() - end sub) - end function - - @async - @it("reject AA with subtype") - function _() - promise = promises.reject({ - subType: "Node" - }) - promises.onCatch(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, { - subType: "Node" - }) - m.testSuite.done() - end sub) - end function - - @async - @it("resolved AA with children array") - function _() - promise = promises.resolve({ - children: [{ subType: "Node" }] - }) - promises.onThen(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, { - children: [{ subType: "Node" }] - }) - m.testSuite.done() - end sub) - end function - - @async - @it("reject AA with children array") - function _() - promise = promises.reject({ - children: [{ subType: "Node" }] - }) - promises.onCatch(promise, sub(result as dynamic) - m.testSuite.assertEqual(result, { - children: [{ subType: "Node" }] - }) - m.testSuite.done() - end sub) - end function - - @async - @it("resolved SgNode") - function _() - testNode = createNode("Node") - promise = promises.resolve(testNode) - promises.onThen(promise, sub(result as dynamic, context as dynamic) - m.testSuite.assertTrue(context.isSameNode(result)) - m.testSuite.done() - end sub, testNode) - end function - - @async - @it("reject SgNode") - function _() - testNode = createNode("Node") - promise = promises.reject(testNode) - promises.onCatch(promise, sub(result as dynamic, context as dynamic) - m.testSuite.assertTrue(context.isSameNode(result)) - m.testSuite.done() - end sub, testNode) - end function - - @async(60000) - @it("unravels deep promise chain without crashing due to stackoverflow") - function _() - 'this function creates a promise that depends on another promise (until we hit a max) - doWork = function(context) - 'if we hit the max, resolve the promise and unravel the entire stack - if context.currentCount > 10000 - return promises.resolve(true) - end if - context.currentCount = context.currentCount + 1 - - 'return a promise that depends on another future promise - return promises.onThen(promises.resolve(true), function(result, context) - doWork = context.doWork - return doWork(context) - end function, context) - end function - - promises.chain(promises.resolve(true), { - currentCount: 0, - doWork: doWork - }).then(function(result, context) - doWork = context.doWork - return doWork(context) - end function).then(function(result, context) - m.testSuite.done() - end function).catch(function(error, context) - print "error", error, FormatJson(error.backtrace) - end function).toPromise() - - end function - end class + @SGNode("test") + @suite + class PromisesTests extends rooibos.BaseTestSuite + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.create()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("create promise") + sub _() + promise = promises.create() + m.assertTrue(promises.isPromise(promise)) + end sub + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.isPromise()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("promise validation") + sub _() + m.assertTrue(promises.isPromise(promises.create())) + promiseNode = createNode("node", { promiseState: 0 }) + m.assertTrue(promises.isPromise(promiseNode)) + notPromise = createNode() + m.assertFalse(promises.isPromise(notPromise)) + m.assertFalse(promises.isPromise(invalid)) + end sub + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.isComplete()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("promise settlement check") + sub _() + m.assertFalse(promises.isComplete(promises.create())) + m.assertTrue(promises.isComplete(promises.resolve({}))) + m.assertTrue(promises.isComplete(promises.reject({}))) + m.assertFalse(promises.isComplete(createNode())) + m.assertFalse(promises.isComplete(invalid)) + end sub + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.chain()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("promise chain follows happy path") + function _() + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + result: "" + } + + results = promises.chain(promises.resolve(1), context).then(sub(result, context) + context.thenCount++ + context.result = result + end sub).catch(sub(error, context) + context.catchCount++ + end sub).finally(sub(context) + context.finallyCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 1 + catchCount: 0 + finallyCount: 1 + result: 1 + }) + end sub, context) + end function + + @it("handles default then identify passthrough calls") + function _() + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + result: [] + } + + results = promises.chain(promises.resolve(1), context).then(function(result, context) + context.thenCount++ + context.result.push(result) + return 2 + end function).then().then().then(function(result, context) + context.thenCount++ + context.result.push(result) + end function).catch(sub(error, context) + context.catchCount++ + end sub).finally(sub(context) + context.finallyCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 2 + catchCount: 0 + finallyCount: 1 + result: [1, 2] + }) + end sub, context) + end function + + @it("calls thens in order") + function _() + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + result: [] + } + + results = promises.chain(promises.resolve(1), context).then(function(result, context) + context.thenCount++ + context.result.push(result) + return 2 + end function).then(function(result, context) + context.thenCount++ + context.result.push(result) + return 3 + end function).then(function(result, context) + context.thenCount++ + context.result.push(result) + return 4 + end function).then(function(result, context) + context.thenCount++ + context.result.push(result) + end function).catch(sub(error, context) + context.catchCount++ + end sub).finally(sub(context) + context.finallyCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 4 + catchCount: 0 + finallyCount: 1 + result: [1, 2, 3, 4] + }) + end sub, context) + end function + + @it("calls catches in order") + function _() + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + result: [] + } + + results = promises.chain(promises.reject(1), context).then(function(result, context) + context.thenCount++ + context.result.push(invalid) + end function).catch(function(result, context) + context.catchCount++ + context.result.push(result) + return promises.reject(2) + end function).catch(function(result, context) + context.catchCount++ + context.result.push(result) + return promises.reject(3) + end function).catch(function(result, context) + context.catchCount++ + context.result.push(result) + return promises.reject(4) + end function).catch(function(result, context) + context.catchCount++ + context.result.push(result) + end function).finally(sub(context) + context.finallyCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 0 + catchCount: 4 + finallyCount: 1 + result: [1, 2, 3, 4] + }) + end sub, context) + end function + + @it("handles default then thrower passthrough calls") + function _() + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + result: [] + } + + results = promises.chain(promises.reject(1), context).then(function(result, context) + context.thenCount++ + context.result.push(invalid) + end function).catch(function(result, context) + context.catchCount++ + context.result.push(result) + return promises.reject(2) + end function).catch().catch().catch(function(result, context) + context.catchCount++ + context.result.push(result) + end function).finally(sub(context) + context.finallyCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 0 + catchCount: 2 + finallyCount: 1 + result: [1, 2] + }) + end sub, context) + end function + + @it("calls finally in order") + function _() + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + result: [] + } + + results = promises.chain(promises.resolve(1), context).then(function(result, context) + context.thenCount++ + context.result.push(result) + return 2 + end function).then(function(result, context) + context.thenCount++ + context.result.push(result) + return 3 + end function).then(function(result, context) + context.thenCount++ + context.result.push(result) + return 4 + end function).then(function(result, context) + context.thenCount++ + context.result.push(result) + end function).catch(sub(error, context) + context.catchCount++ + end sub).finally(function(context) + context.finallyCount++ + end function).finally(function(context) + context.finallyCount++ + end function).finally(function(context) + context.finallyCount++ + end function).finally(function(context) + context.finallyCount++ + end function).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 4 + catchCount: 0 + finallyCount: 4 + result: [1, 2, 3, 4] + }) + end sub, context) + end function + + @it("handles default finally passthrough calls") + function _() + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + result: [] + } + + results = promises.chain(promises.resolve(1), context).then(function(result, context) + context.thenCount++ + context.result.push(result) + return 2 + end function).then(function(result, context) + context.thenCount++ + context.result.push(result) + return 3 + end function).then(function(result, context) + context.thenCount++ + context.result.push(result) + return 4 + end function).then(function(result, context) + context.thenCount++ + context.result.push(result) + end function).catch(sub(error, context) + context.catchCount++ + end sub).finally(function(context) + context.finallyCount++ + end function).finally().finally().finally(function(context) + context.finallyCount++ + end function).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 4 + catchCount: 0 + finallyCount: 2 + result: [1, 2, 3, 4] + }) + end sub, context) + end function + + @it("skips thens when promise is rejected") + function _() + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + result: [] + error: "" + } + + results = promises.chain(promises.resolve(1), context).then(function(result, context) + context.thenCount++ + context.result.push(result) + return promises.reject("rejected") + end function).then(function(result, context) + context.thenCount++ + context.result.push(invalid) + end function).then(function(result, context) + context.thenCount++ + context.result.push(invalid) + end function).then(function(result, context) + context.thenCount++ + context.result.push(invalid) + end function).catch(sub(error, context) + context.catchCount++ + context.error = error + end sub).finally(sub(context) + context.finallyCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 1 + catchCount: 1 + finallyCount: 1 + result: [1] + error: "rejected" + }) + end sub, context) + end function + + @it("ignores return value from finally when not crash or rejected promise") + function _() + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + result: "" + } + + results = promises.chain(promises.resolve("I am happy"), context).finally(function(context) + context.finallyCount++ + return "I am sad" + end function).then(sub(result as dynamic, context as dynamic) + context.thenCount++ + context.result = result + end sub).catch(sub(result as dynamic, context as dynamic) + context.catchCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 1 + catchCount: 0 + finallyCount: 1 + result: "I am happy" + }) + end sub, context) + end function + + @it("finally does not prevent rejection from propagating down the chain") + function _() + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + errorMessage: "" + } + + results = promises.chain(promises.reject("Crash"), context).finally(sub(context) + context.finallyCount++ + end sub).then(sub(result as dynamic, context as dynamic) + context.thenCount++ + end sub).catch(sub(result as dynamic, context as dynamic) + context.catchCount++ + context.errorMessage = result + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 0 + catchCount: 1 + finallyCount: 1 + errorMessage: "Crash" + }) + end sub, context) + end function + + @it("finally that returns resolved promises does not prevent rejection from propagating down the chain") + function _() + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + errorMessage: "" + } + + results = promises.chain(promises.reject("Crash"), context).finally(function(context) + context.finallyCount++ + return promises.resolve(true) + end function).then(sub(result as dynamic, context as dynamic) + context.thenCount++ + end sub).catch(sub(result as dynamic, context as dynamic) + context.catchCount++ + context.errorMessage = result + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 0 + catchCount: 1 + finallyCount: 1 + errorMessage: "Crash" + }) + end sub, context) + end function + + @it("finally that returns value does not prevent rejection from propagating down the chain") + function _() + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + errorMessage: "" + } + + results = promises.chain(promises.reject("Crash"), context).finally(function(context) + context.finallyCount++ + return true + end function).then(sub(result as dynamic, context as dynamic) + context.thenCount++ + end sub).catch(sub(result as dynamic, context as dynamic) + context.catchCount++ + context.errorMessage = result + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 0 + catchCount: 1 + finallyCount: 1 + errorMessage: "Crash" + }) + end sub, context) + end function + + @it("returned rejection in finally is propagated down the chain") + function _() + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + errorMessage: "" + } + + results = promises.chain(promises.reject("Crash"), context).finally(function(context) + context.finallyCount++ + return promises.reject("error in finally") + end function).then(sub(result as dynamic, context as dynamic) + context.thenCount++ + end sub).catch(sub(result as dynamic, context as dynamic) + context.catchCount++ + context.errorMessage = result + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 0 + catchCount: 1 + finallyCount: 1 + errorMessage: "error in finally" + }) + end sub, context) + end function + + @it("crash in finally is propagated down the chain") + function _() + context = { + thenCount: 0 + catchCount: 0 + finallyCount: 0 + errorMessage: "" + } + + results = promises.chain(promises.reject("Crash"), context).finally(function(context) + context.finallyCount++ + throw "error in finally" + end function).then(sub(result as dynamic, context as dynamic) + context.thenCount++ + end sub).catch(sub(result as dynamic, context as dynamic) + context.catchCount++ + context.errorMessage = result.message + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 0 + catchCount: 1 + finallyCount: 1 + errorMessage: "error in finally" + }) + end sub, context) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.all()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("handled non-array") + function _() + context = { + thenCount: 0 + catchCount: 0 + errorMessage: "" + backtrace: invalid + } + + results = promises.chain(promises.all(invalid), context).then(sub(result, context) + context.thenCount++ + end sub).catch(function(error, context) + context.catchCount++ + context.errorMessage = error.message + context.backtrace = error.backtrace + return true + end function).toPromise() + + return promises.onFinally(results, sub(context) + backtrace = context.backtrace + context.delete("backtrace") + m.testSuite.assertEqual(context, { + thenCount: 0 + catchCount: 1 + errorMessage: "Did not supply an array" + }) + m.testSuite.assertNotInvalid(backtrace) + end sub, context) + end function + + @it("handled empty array") + function _() + context = { + thenCount: 0 + catchCount: 0 + result: invalid + } + + results = promises.chain(promises.all([]), context).then(sub(result, context) + context.thenCount++ + context.result = result + end sub).catch(sub(_, context) + context.catchCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 1 + catchCount: 0 + result: [] + }) + end sub, context) + end function + + @it("resolving all promises") + function _() + context = { + thenCount: 0 + catchCount: 0 + result: invalid + } + + results = promises.chain(promises.all([ + promises.resolve(1) + promises.resolve(2) + promises.resolve(3) + ]), context).then(sub(result, context) + context.thenCount++ + context.result = result + end sub).catch(sub(_, context) + context.catchCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 1 + catchCount: 0 + result: [1, 2, 3] + }) + end sub, context) + end function + + @it("resolving works with non-promise entire all promises") + function _() + context = { + thenCount: 0 + catchCount: 0 + result: invalid + } + + results = promises.chain(promises.all([ + promises.resolve(1) + 2 + promises.resolve(3) + ]), context).then(sub(result, context) + context.thenCount++ + context.result = result + end sub).catch(sub(_, context) + context.catchCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 1 + catchCount: 0 + result: [1, 2, 3] + }) + end sub, context) + end function + + @it("resolving works with all non-promise entires all promises") + function _() + context = { + thenCount: 0 + catchCount: 0 + result: invalid + } + + results = promises.chain(promises.all([ + 1 + 2 + 3 + ]), context).then(sub(result, context) + context.thenCount++ + context.result = result + end sub).catch(sub(_, context) + context.catchCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 1 + catchCount: 0 + result: [1, 2, 3] + }) + end sub, context) + end function + + @it("rejecting all promises") + function _() + context = { + thenCount: 0 + catchCount: 0 + result: "" + } + + results = promises.chain(promises.all([ + promises.resolve(1) + promises.reject(2) + promises.resolve(3) + ]), context).then(sub(_, context) + context.thenCount++ + end sub).catch(sub(error, context) + context.catchCount++ + context.result = error + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 0 + catchCount: 1 + result: 2 + }) + end sub, context) + end function + + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.allSettled()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("handled non-array") + function _() + context = { + thenCount: 0 + catchCount: 0 + errorMessage: "" + backtrace: invalid + } + + results = promises.chain(promises.allSettled(invalid), context).then(sub(_, context) + context.thenCount++ + end sub).catch(function(error, context) + context.catchCount++ + context.errorMessage = error.message + context.backtrace = error.backtrace + end function).toPromise() + + return promises.onFinally(results, sub(context) + backtrace = context.backtrace + context.delete("backtrace") + m.testSuite.assertEqual(context, { + thenCount: 0 + catchCount: 1 + errorMessage: "Did not supply an array" + }) + m.testSuite.assertNotInvalid(backtrace) + end sub, context) + end function + + @it("handled empty array") + function _() + context = { + thenCount: 0 + catchCount: 0 + result: invalid + } + + results = promises.chain(promises.allSettled([]), context).then(sub(result, context) + context.thenCount++ + context.result = result + end sub).catch(sub(_, context) + context.catchCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 1 + catchCount: 0 + result: [] + }) + end sub, context) + end function + + @it("resolving all promises in allSettled") + function _() + context = { + thenCount: 0 + catchCount: 0 + result: invalid + } + + results = promises.chain(promises.allSettled([ + promises.resolve(1) + promises.resolve(2) + promises.resolve(3) + ]), context).then(sub(result, context) + context.thenCount++ + context.result = result + end sub).catch(sub(_, context) + context.catchCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 1 + catchCount: 0 + result: [ + { status: promises.PromiseState.resolved, value: 1 } + { status: promises.PromiseState.resolved, value: 2 } + { status: promises.PromiseState.resolved, value: 3 } + ] + }) + end sub, context) + end function + + @it("resolving works with non-promise entire in allSettled") + function _() + context = { + thenCount: 0 + catchCount: 0 + result: invalid + } + + results = promises.chain(promises.allSettled([ + promises.resolve(1) + 2 + promises.resolve(3) + ]), context).then(sub(result, context) + context.thenCount++ + context.result = result + end sub).catch(sub(_, context) + context.catchCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 1 + catchCount: 0 + result: [ + { status: promises.PromiseState.resolved, value: 1 } + { status: promises.PromiseState.resolved, value: 2 } + { status: promises.PromiseState.resolved, value: 3 } + ] + }) + end sub, context) + end function + + @it("resolving works with all non-promise entire in allSettled") + function _() + context = { + thenCount: 0 + catchCount: 0 + result: invalid + } + + results = promises.chain(promises.allSettled([ + 1 + 2 + 3 + ]), context).then(sub(result, context) + context.thenCount++ + context.result = result + end sub).catch(sub(_) + context.catchCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 1 + catchCount: 0 + result: [ + { status: promises.PromiseState.resolved, value: 1 } + { status: promises.PromiseState.resolved, value: 2 } + { status: promises.PromiseState.resolved, value: 3 } + ] + }) + end sub, context) + end function + + @it("rejecting a promise in allSettled") + function _() + context = { + thenCount: 0 + catchCount: 0 + result: invalid + } + + results = promises.chain(promises.allSettled([ + promises.resolve(1) + promises.reject(2) + promises.resolve(3) + ]), context).then(sub(result, context) + context.thenCount++ + context.result = result + end sub).catch(sub(_) + context.catchCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 1 + catchCount: 0 + result: [ + { status: promises.PromiseState.resolved, value: 1 } + { status: promises.PromiseState.rejected, reason: 2 } + { status: promises.PromiseState.resolved, value: 3 } + ] + }) + end sub, context) + end function + + @it("rejecting all promises in allSettled") + function _() + context = { + thenCount: 0 + catchCount: 0 + result: invalid + } + + results = promises.chain(promises.allSettled([ + promises.reject(1) + promises.reject(2) + promises.reject(3) + ]), context).then(sub(result, context) + context.thenCount++ + context.result = result + end sub).catch(sub(_) + context.catchCount++ + end sub).toPromise() + + return promises.onFinally(results, sub(context) + m.testSuite.assertEqual(context, { + thenCount: 1 + catchCount: 0 + result: [ + { status: promises.PromiseState.rejected, reason: 1 } + { status: promises.PromiseState.rejected, reason: 2 } + { status: promises.PromiseState.rejected, reason: 3 } + ] + }) + end sub, context) + end function + + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.any()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("handled non-array") + function _() + context = { + thenCount: 0 + catchCount: 0 + errors: invalid + errorMessage: "" + backtrace: invalid + } + + results = promises.chain(promises.any(invalid), context).then(sub(_, context) + context.thenCount++ + end sub).catch(sub(error, context) + context.catchCount++ + context.errors = error.errors + context.errorMessage = error.message + context.backtrace = error.backtrace + end sub).toPromise() + + return promises.onFinally(results, sub(context) + backtrace = context.backtrace + context.delete("backtrace") + m.testSuite.assertEqual(context, { + thenCount: 0 + catchCount: 1 + errors: [] + errorMessage: "All promises were rejected" + }) + m.testSuite.assertNotInvalid(backtrace) + end sub, context) + end function + + @it("handled empty array") + function _() + context = { + thenCount: 0 + catchCount: 0 + errors: invalid + errorMessage: "" + backtrace: invalid + } + + results = promises.chain(promises.any([]), context).then(sub(_, context) + context.thenCount++ + end sub).catch(sub(error, context) + context.catchCount++ + context.errors = error.errors + context.errorMessage = error.message + context.backtrace = error.backtrace + end sub).toPromise() + + return promises.onFinally(results, sub(context) + backtrace = context.backtrace + context.delete("backtrace") + m.testSuite.assertEqual(context, { + thenCount: 0 + catchCount: 1 + errors: [] + errorMessage: "All promises were rejected" + }) + m.testSuite.assertNotInvalid(backtrace) + end sub, context) + end function + + @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 + + @it("handles a pre-resolved promise") + function _() + promiseArray = [ + promises.create() + promises.resolve(2) + promises.create() + ] + + return 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]) + end sub).toPromise() + end function + + @it("handles a non-promise value amongst pending promises") + function _() + promiseArray = [ + promises.create() + 2 + promises.create() + ] + + return 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]) + end sub).toPromise() + end function + + @it("handles a non-promise value amongst rejected promises") + function _() + promiseArray = [ + promises.reject(1) + 2 + promises.reject(2) + ] + + return 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).toPromise() + end function + + @it("handles a first pre-resolved promise") + function _() + promiseArray = [ + promises.resolve(1) + promises.resolve(2) + promises.resolve(2) + ] + + return 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).toPromise() + end function + + @async + @it("handles first pre-resolved promise along with a non-promise value") + function _() + promiseArray = [ + promises.resolve(1) + 2 + promises.resolve(2) + ] + + return 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).toPromise() + end function + + @it("handles all promises being pre-rejected") + function _() + promiseArray = [ + promises.reject("1") + promises.reject("2") + promises.reject("3") + ] + + return 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).toPromise() + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.race()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("handled non-array") + function _() + return 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).toPromise() + end function + + @it("handled empty array") + function _() + return 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).toPromise() + end function + + @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 + + @it("handles a pre-resolved promise") + function _() + promiseArray = [ + promises.create() + promises.resolve(2) + promises.create() + ] + + return 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]) + end sub).toPromise() + end function + + @it("handles a non-promise value amongst pending promises") + function _() + 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]) + end sub).toPromise() + end function + + @it("handles a non-promise value amongst rejected promises") + function _() + promiseArray = [ + promises.reject(1) + 2 + promises.reject(3) + ] + + return 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).toPromise() + end function + + @it("handles a first pre-resolved promise") + function _() + promiseArray = [ + promises.resolve(1) + promises.resolve(2) + promises.resolve(3) + ] + + return 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).toPromise() + end function + + @it("handles first pre-resolved promise along with a non-promise value") + function _() + promiseArray = [ + promises.resolve(1) + 2 + promises.resolve(3) + ] + + return 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).toPromise() + end function + + @it("handles all promises being pre-rejected") + function _() + promiseArray = [ + promises.reject("1") + promises.reject("2") + promises.reject("3") + ] + + return 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).toPromise() + end function + + @it("handled a the first promise to resolve") + function _() + 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).toPromise() + end function + + @it("handled a the first promise to reject") + function _() + 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).toPromise() + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.onThen()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @async + @it("catches resolved promises") + sub _() + promises.onThen(promises.resolve("resolved"), sub(value) + m.testSuite.assertEqual(value, "resolved") + m.testSuite.done() + end sub) + end sub + + @async + @only() + @it("recovers when did NOT send context, but DID define the parameter (testing multiple param types") + sub _() + getGlobalAA().myCount = 0 + promises.onFinally(promises.all([ + 'these will work + promises.onThen(promises.resolve("resolved"), sub(value, context): m.myCount++ : end sub), + promises.onThen(promises.resolve("resolved"), sub(value, context as dynamic): m.myCount++ : end sub), + promises.onThen(promises.resolve("resolved"), sub(value, context as object): m.myCount++ : end sub), + promises.onThen(promises.resolve("resolved"), sub(value, context as integer): m.myCount++ : end sub), + promises.onThen(promises.resolve("resolved"), sub(value, context as longinteger): m.myCount++ : end sub), + promises.onThen(promises.resolve("resolved"), sub(value, context as float): m.myCount++ : end sub), + promises.onThen(promises.resolve("resolved"), sub(value, context as double): m.myCount++ : end sub), + promises.onThen(promises.resolve("resolved"), sub(value, context as boolean): m.myCount++ : end sub), + 'these will not, that's okay, can't solve everything + promises.onThen(promises.resolve("resolved"), sub(value, context as function): m.myCount++ : end sub), + promises.onThen(promises.resolve("resolved"), sub(value, context as string): m.myCount++ : end sub), + ]), sub(results) + m.testSuite.assertEqual(getGlobalAA().myCount, 8) + m.testSuite.done() + end sub) + end sub + + @async + @it("recovers when DID send context, but did NOT define the parameter") + sub _() + context = {} + promises.onThen(promises.resolve("resolved"), sub(value) + m.testSuite.assertEqual(value, "resolved") + m.testSuite.done() + end sub, context) + end sub + + @async + @it("handles empty callbacks") + sub _() + promises.onThen(promises.onThen(promises.resolve("resolved")), sub(value) + m.testSuite.assertEqual(value, "resolved") + m.testSuite.done() + end sub) + end sub + + @async + @it("does not response to rejected promises") + sub _() + context = { thenCount: 0 } + + promise = promises.onThen(promises.reject("rejected"), sub(value, context) + context.thenCount++ + end sub, context) + + promises.onFinally(promise, sub(context) + m.testSuite.assertEqual(context, { thenCount: 0 }) + m.testSuite.done() + end sub, context) + end sub + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.onCatch()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @async + @it("catches rejected promises") + sub _() + promises.onCatch(promises.reject("rejected"), sub(value) + m.testSuite.assertEqual(value, "rejected") + m.testSuite.done() + end sub) + end sub + + @async + @it("recovers when did NOT send context, but DID define the parameter") + sub _() + promises.onCatch(promises.reject("rejected"), sub(value, context) + m.testSuite.assertEqual(value, "rejected") + m.testSuite.done() + end sub) + end sub + + @async + @only() + @it("recovers when did NOT send context, but DID define the parameter (testing multiple param types") + sub _() + getGlobalAA().myCount = 0 + promises.onFinally(promises.all([ + 'these will work + promises.onCatch(promises.reject("rejected"), sub(value, context): m.myCount++ : end sub), + promises.onCatch(promises.reject("rejected"), sub(value, context as dynamic): m.myCount++ : end sub), + promises.onCatch(promises.reject("rejected"), sub(value, context as object): m.myCount++ : end sub), + promises.onCatch(promises.reject("rejected"), sub(value, context as integer): m.myCount++ : end sub), + promises.onCatch(promises.reject("rejected"), sub(value, context as longinteger): m.myCount++ : end sub), + promises.onCatch(promises.reject("rejected"), sub(value, context as float): m.myCount++ : end sub), + promises.onCatch(promises.reject("rejected"), sub(value, context as double): m.myCount++ : end sub), + promises.onCatch(promises.reject("rejected"), sub(value, context as boolean): m.myCount++ : end sub), + 'these will not, that's okay, can't solve everything + promises.onCatch(promises.reject("rejected"), sub(value, context as function): m.myCount++ : end sub), + promises.onCatch(promises.reject("rejected"), sub(value, context as string): m.myCount++ : end sub), + ]), sub(results) + m.testSuite.assertEqual(getGlobalAA().myCount, 8) + m.testSuite.done() + end sub) + end sub + + + @async + @it("recovers when DID send context, but did NOT define the parameter") + sub _() + context = {} + promises.onCatch(promises.reject("rejected"), sub(value) + m.testSuite.assertEqual(value, "rejected") + m.testSuite.done() + end sub, context) + end sub + + @async + @it("handles empty callbacks") + sub _() + promises.onCatch(promises.onCatch(promises.reject("rejected")), sub(value) + m.testSuite.assertEqual(value, "rejected") + m.testSuite.done() + end sub) + end sub + + @async + @it("does not response to resolved promises") + sub _() + context = { catchCount: 0 } + + promise = promises.onCatch(promises.resolve("resolved"), sub(value, context) + context.catchCount++ + end sub, context) + + promises.onFinally(promise, sub(context) + m.testSuite.assertEqual(context, { catchCount: 0 }) + m.testSuite.done() + end sub, context) + end sub + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.onFinally()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @async + @it("handles resolved promise") + sub _() + promise = promises.onFinally(promises.resolve("resolved"), sub() + end sub) + + promises.onThen(promise, sub(value) + m.testSuite.assertEqual(value, "resolved") + m.testSuite.done() + end sub) + end sub + + @async + @it("recovers when DID send context, but did NOT define the parameter") + sub _() + context = {} + promises.onFinally(promises.resolve("resolved"), sub() + m.testSuite.done() + end sub, context) + end sub + + @async + @only() + @it("recovers when did NOT send context, but DID define the parameter (testing multiple param types") + sub _() + getGlobalAA().myCount = 0 + promises.onFinally(promises.all([ + 'these will work + promises.onFinally(promises.resolve("resolved"), sub(context): m.myCount++ : end sub), + promises.onFinally(promises.resolve("resolved"), sub(context as dynamic): m.myCount++ : end sub), + promises.onFinally(promises.resolve("resolved"), sub(context as object): m.myCount++ : end sub), + promises.onFinally(promises.resolve("resolved"), sub(context as integer): m.myCount++ : end sub), + promises.onFinally(promises.resolve("resolved"), sub(context as longinteger): m.myCount++ : end sub), + promises.onFinally(promises.resolve("resolved"), sub(context as float): m.myCount++ : end sub), + promises.onFinally(promises.resolve("resolved"), sub(context as double): m.myCount++ : end sub), + promises.onFinally(promises.resolve("resolved"), sub(context as boolean): m.myCount++ : end sub), + 'these will not, that's okay, can't solve everything + promises.onFinally(promises.resolve("resolved"), sub(context as function): m.myCount++ : end sub), + promises.onFinally(promises.resolve("resolved"), sub(context as string): m.myCount++ : end sub), + ]), sub(results) + m.testSuite.assertEqual(getGlobalAA().myCount, 8) + m.testSuite.done() + end sub) + end sub + + @async + @it("handles resolved promise with empty callback") + sub _() + promise = promises.onFinally(promises.onFinally(promises.resolve("resolved")), sub() + end sub) + + promises.onThen(promise, sub(value) + m.testSuite.assertEqual(value, "resolved") + m.testSuite.done() + end sub) + end sub + + @async + @it("handles rejected promise") + sub _() + promise = promises.onFinally(promises.reject("rejected"), sub() + end sub) + + promises.onCatch(promise, sub(value) + m.testSuite.assertEqual(value, "rejected") + m.testSuite.done() + end sub) + end sub + + @async + @it("handles rejected promise with empty callback") + sub _() + promise = promises.onFinally(promises.onFinally(promises.reject("rejected")), sub() + end sub) + + promises.onCatch(promise, sub(value) + m.testSuite.assertEqual(value, "rejected") + m.testSuite.done() + end sub) + end sub + + @async + @it("handles returning as rejected promise from the finally") + sub _() + promise = promises.onFinally(promises.reject("rejected"), function() + return promises.reject("new rejected promise") + end function) + + promises.onCatch(promise, sub(value) + m.testSuite.assertEqual(value, "new rejected promise") + m.testSuite.done() + end sub) + end sub + + @async + @it("handles rejecting with a crash in the finally") + sub _() + promise = promises.onFinally(promises.reject("rejected"), sub() + throw "new rejected promise" + end sub) + + promises.onCatch(promise, sub(value) + m.testSuite.assertEqual(value.message, "new rejected promise") + m.testSuite.assertNotInvalid(value.backtrace) + m.testSuite.done() + end sub) + end sub + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.enableCrashLogging()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @beforeEach + sub beforeEachenableCrashLogging() + m.global.removeField("__promises__crashLoggingEnabled") + m.node.delete("formatStackTraceCalled") + end sub + + @afterEach + sub afterEachenableCrashLogging() + m.global.removeField("__promises__crashLoggingEnabled") + m.node.delete("formatStackTraceCalled") + end sub + + @it("properly adds and removes the setting from global") + function _() + m.assertFalse(m.global.hasField("__promises__crashLoggingEnabled")) + promises.configuration.enableCrashLogging(true) + m.assertNodeContainsFields(m.global, { "__promises__crashLoggingEnabled": true }) + promises.configuration.enableCrashLogging(false) + m.assertFalse(m.global.hasField("__promises__crashLoggingEnabled")) + end function + + @it("enableCrashLogging(true) onThen() no context provided should not log") + function _() + promises.configuration.enableCrashLogging(true) + + m.node.formatStackTraceCalled = 0 + m.stubCall(promises.internal.formatStackTrace, function(error, message) + m.formatStackTraceCalled++ + return "" + end function) + + return promises.chain(promises.resolve("resolved")).then(sub(value) + throw "my error" + end sub).catch(sub(error) + m.testSuite.assertEqual(m.formatStackTraceCalled, 0) + end sub).toPromise() + end function + + @it("enableCrashLogging(true) onThen() context was provided") + function _() + promises.configuration.enableCrashLogging(true) + + m.node.formatStackTraceCalled = 0 + m.stubCall(promises.internal.formatStackTrace, function(error, message) + m.testSuite.assertNotInvalid(error.message) + m.testSuite.assertNotInvalid(error.backtrace) + m.testSuite.assertEqual(message, "Divide by Zero.") + m.formatStackTraceCalled++ + return "" + end function) + + return promises.chain(promises.resolve("resolved"), {}).then(sub(value, context) + test = 1 / 0 + end sub).catch(sub(error, context) + m.testSuite.assertEqual(m.formatStackTraceCalled, 1) + end sub).toPromise() + end function + + @it("enableCrashLogging(true) onCatch() no context provided") + function _() + promises.configuration.enableCrashLogging(true) + + m.node.formatStackTraceCalled = 0 + m.stubCall(promises.internal.formatStackTrace, function(_, _) + m.formatStackTraceCalled++ + return "" + end function) + + return promises.chain(promises.reject("rejected")).catch(sub(error) + test = 1 / 0 + end sub).catch(sub(error) + m.testSuite.assertEqual(m.formatStackTraceCalled, 1) + end sub).toPromise() + end function + + @it("enableCrashLogging(true) onCatch() context was provided") + function _() + promises.configuration.enableCrashLogging(true) + + m.node.formatStackTraceCalled = 0 + m.stubCall(promises.internal.formatStackTrace, function(_, _) + m.formatStackTraceCalled++ + return "" + end function) + + return promises.chain(promises.reject("rejected"), {}).catch(sub(error, context) + test = 1 / 0 + end sub).catch(sub(error, context) + m.testSuite.assertEqual(m.formatStackTraceCalled, 1) + end sub).toPromise() + end function + + @it("enableCrashLogging(true) onFinally() no context provided") + function _() + promises.configuration.enableCrashLogging(true) + + m.node.formatStackTraceCalled = 0 + m.stubCall(promises.internal.formatStackTrace, function(_, _) + m.formatStackTraceCalled++ + return "" + end function) + + return promises.chain(promises.resolve("resolved")).finally(sub() + test = 1 / 0 + end sub).catch(sub(error) + m.testSuite.assertEqual(m.formatStackTraceCalled, 1) + end sub).toPromise() + end function + + @it("enableCrashLogging(true) onFinally() context was provided") + function _() + promises.configuration.enableCrashLogging(true) + + m.node.formatStackTraceCalled = 0 + m.stubCall(promises.internal.formatStackTrace, function(_, _) + m.formatStackTraceCalled++ + return "" + end function) + + return promises.chain(promises.resolve("resolved"), {}).finally(sub(context) + test = 1 / 0 + end sub).catch(sub(error, context) + m.testSuite.assertEqual(m.formatStackTraceCalled, 1) + end sub).toPromise() + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("does not resolve to soon or too late") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @async + @it("timer promise") + function _() + timerDurationInMillis = 125 + promise = sleepPromise(timerDurationInMillis / 1000) + promises.onThen(promise, sub(_ as dynamic, ctx as dynamic) + elapsedTimeInMillis = ctx.timespan.totalMilliseconds() + ? "elapsed time to resolve promise:" + elapsedTimeInMillis.tostr() + tolerance = ctx.timerDurationInMillis * 0.2 + msg = "did not settle within 10% tolerance of timer duration" + m.testSuite.assertTrue(ctx.timerDurationInMillis - tolerance <= elapsedTimeInMillis, msg) + m.testSuite.assertTrue(ctx.timerDurationInMillis + tolerance >= elapsedTimeInMillis, msg) + m.testSuite.done() + end sub, { + timespan: createObject("roTimespan") + timerDurationInMillis: timerDurationInMillis + }) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.try()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("returns a the same promise if the callback returns a promise") + function _() + resolvedPromiseParam = promises.resolve(1) + resolvedPromise = promises.try(function(arg) + return arg + end function, [resolvedPromiseParam]) + + m.assertTrue(promises.isPromise(resolvedPromise)) + m.assertTrue(resolvedPromise.isSameNode(resolvedPromiseParam)) + + rejectedPromiseParam = promises.reject("error") + rejectedPromise = promises.try(function(arg) + return arg + end function, [rejectedPromiseParam]) + + m.assertTrue(promises.isPromise(rejectedPromise)) + m.assertTrue(rejectedPromise.isSameNode(rejectedPromiseParam)) + + return promises.onThen(promises.allSettled([resolvedPromise, rejectedPromise]), sub(results) + m.testSuite.assertEqual(results, [ + { status: promises.PromiseState.resolved, value: 1 } + { status: promises.PromiseState.rejected, reason: "error" } + ]) + end sub) + end function + + @it("returns a rejected promise if the callback throws an error") + function _() + promise = promises.try(function() + throw "error" + end function) + + m.assertTrue(promises.isPromise(promise)) + + return promises.onCatch(promise, sub(error) + m.testSuite.assertEqual(error.message, "error") + m.testSuite.assertNotInvalid(error.backTrace) + end sub) + end function + + @it("calls the callback when supplied with no augments wrapped in a promise") + function _() + promise = promises.try(function() + return true + end function) + + m.assertTrue(promises.isPromise(promise)) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, true) + end sub) + end function + + + @it("calls the callback when supplied empty augments") + function _() + promise = promises.try(function() + return true + end function, []) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, true) + end sub) + end function + + @it("calls the callback when supplied with 1 augment") + function _() + promise = promises.try(function(a) + return [a] + end function, [1]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1]) + end sub) + end function + + @it("calls the callback when supplied with 2 augments") + function _() + promise = promises.try(function(a, b) + return [a, b] + end function, [1, 2]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2]) + end sub) + end function + + @it("calls the callback when supplied with 3 augments") + function _() + promise = promises.try(function(a, b, c) + return [a, b, c] + end function, [1, 2, 3]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3]) + end sub) + end function + + @it("calls the callback when supplied with 4 augments") + function _() + promise = promises.try(function(a, b, c, d) + return [a, b, c, d] + end function, [1, 2, 3, 4]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4]) + end sub) + end function + + @it("calls the callback when supplied with 5 augments") + function _() + promise = promises.try(function(a, b, c, d, e) + return [a, b, c, d, e] + end function, [1, 2, 3, 4, 5]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5]) + end sub) + end function + + @it("calls the callback when supplied with 6 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f) + return [a, b, c, d, e, f] + end function, [1, 2, 3, 4, 5, 6]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6]) + end sub) + end function + + @it("calls the callback when supplied with 7 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g) + return [a, b, c, d, e, f, g] + end function, [1, 2, 3, 4, 5, 6, 7]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7]) + end sub) + end function + + @it("calls the callback when supplied with 8 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h) + return [a, b, c, d, e, f, g, h] + end function, [1, 2, 3, 4, 5, 6, 7, 8]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8]) + end sub) + end function + + @it("calls the callback when supplied with 9 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i) + return [a, b, c, d, e, f, g, h, i] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9]) + end sub) + end function + + @it("calls the callback when supplied with 10 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j) + return [a, b, c, d, e, f, g, h, i, j] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + end sub) + end function + + @it("calls the callback when supplied with 11 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k) + return [a, b, c, d, e, f, g, h, i, j, k] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + end sub) + end function + + @it("calls the callback when supplied with 12 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l) + return [a, b, c, d, e, f, g, h, i, j, k, l] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + end sub) + end function + + @it("calls the callback when supplied with 13 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) + end sub) + end function + + @it("calls the callback when supplied with 14 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]) + end sub) + end function + + @it("calls the callback when supplied with 15 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) + end sub) + end function + + @it("calls the callback when supplied with 16 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]) + end sub) + end function + + @it("calls the callback when supplied with 17 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]) + end sub) + end function + + @it("calls the callback when supplied with 18 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]) + end sub) + end function + + @it("calls the callback when supplied with 19 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + end sub) + end function + + @it("calls the callback when supplied with 20 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) + end sub) + end function + + @it("calls the callback when supplied with 21 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) + end sub) + end function + + @it("calls the callback when supplied with 22 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]) + end sub) + end function + + @it("calls the callback when supplied with 23 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]) + end sub) + end function + + @it("calls the callback when supplied with 24 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]) + end sub) + end function + + @it("calls the callback when supplied with 25 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]) + end sub) + end function + + @it("calls the callback when supplied with 26 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]) + end sub) + end function + + @it("calls the callback when supplied with 27 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]) + end sub) + end function + + @it("calls the callback when supplied with 28 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]) + end sub) + end function + + @it("calls the callback when supplied with 29 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]) + end sub) + end function + + @it("calls the callback when supplied with 30 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]) + end sub) + end function + + @it("calls the callback when supplied with 31 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad, ae) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad, ae] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]) + end sub) + end function + + @it("calls the callback when supplied with 32 augments") + function _() + promise = promises.try(function(a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad, ae, af) + return [a, b, c, d, e, f, g, h, i, j, k, l, mm, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad, ae, af] + end function, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]) + + return promises.onThen(promise, sub(result) + m.testSuite.assertEqual(result, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]) + end sub) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("promises.resolve()/promises.reject()") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @async + @it("resolved invalid") + function _() + promise = promises.resolve(invalid) + promises.onThen(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, invalid) + m.testSuite.done() + end sub) + end function + + @async + @it("reject invalid") + function _() + promise = promises.reject(invalid) + promises.onCatch(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, invalid) + m.testSuite.done() + end sub) + end function + + @async + @it("resolved integer") + function _() + promise = promises.resolve(1) + promises.onThen(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, 1) + m.testSuite.done() + end sub) + end function + + @async + @it("reject integer") + function _() + promise = promises.reject(1) + promises.onCatch(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, 1) + m.testSuite.done() + end sub) + end function + + @async + @it("resolved float") + function _() + promise = promises.resolve(1.1) + promises.onThen(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, 1.1) + m.testSuite.done() + end sub) + end function + + @async + @it("reject float") + function _() + promise = promises.reject(1.1) + promises.onCatch(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, 1.1) + m.testSuite.done() + end sub) + end function + + @async + @it("resolved boolean") + function _() + promise = promises.resolve(true) + promises.onThen(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, true) + m.testSuite.done() + end sub) + end function + + @async + @it("reject boolean") + function _() + promise = promises.reject(true) + promises.onCatch(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, true) + m.testSuite.done() + end sub) + end function + + @async + @it("resolved string") + function _() + promise = promises.resolve("my string") + promises.onThen(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, "my string") + m.testSuite.done() + end sub) + end function + + @async + @it("reject string") + function _() + promise = promises.reject("my string") + promises.onCatch(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, "my string") + m.testSuite.done() + end sub) + end function + + @async + @it("resolved array") + function _() + promise = promises.resolve([1, 2, 3]) + promises.onThen(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, [1, 2, 3]) + m.testSuite.done() + end sub) + end function + + @async + @it("reject array") + function _() + promise = promises.reject([1, 2, 3]) + promises.onCatch(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, [1, 2, 3]) + m.testSuite.done() + end sub) + end function + + @async + @it("resolved AA") + function _() + promise = promises.resolve({ + key: "value" + }) + promises.onThen(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, { + key: "value" + }) + m.testSuite.done() + end sub) + end function + + @async + @it("reject AA") + function _() + promise = promises.reject({ + key: "value" + }) + promises.onCatch(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, { + key: "value" + }) + m.testSuite.done() + end sub) + end function + + @async + @it("resolved AA with subtype") + function _() + promise = promises.resolve({ + subType: "Node" + }) + promises.onThen(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, { + subType: "Node" + }) + m.testSuite.done() + end sub) + end function + + @async + @it("reject AA with subtype") + function _() + promise = promises.reject({ + subType: "Node" + }) + promises.onCatch(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, { + subType: "Node" + }) + m.testSuite.done() + end sub) + end function + + @async + @it("resolved AA with children array") + function _() + promise = promises.resolve({ + children: [{ subType: "Node" }] + }) + promises.onThen(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, { + children: [{ subType: "Node" }] + }) + m.testSuite.done() + end sub) + end function + + @async + @it("reject AA with children array") + function _() + promise = promises.reject({ + children: [{ subType: "Node" }] + }) + promises.onCatch(promise, sub(result as dynamic) + m.testSuite.assertEqual(result, { + children: [{ subType: "Node" }] + }) + m.testSuite.done() + end sub) + end function + + @async + @it("resolved SgNode") + function _() + testNode = createNode("Node") + promise = promises.resolve(testNode) + promises.onThen(promise, sub(result as dynamic, context as dynamic) + m.testSuite.assertTrue(context.isSameNode(result)) + m.testSuite.done() + end sub, testNode) + end function + + @async + @it("reject SgNode") + function _() + testNode = createNode("Node") + promise = promises.reject(testNode) + promises.onCatch(promise, sub(result as dynamic, context as dynamic) + m.testSuite.assertTrue(context.isSameNode(result)) + m.testSuite.done() + end sub, testNode) + end function + + @async(60000) + @it("unravels deep promise chain without crashing due to stackoverflow") + function _() + 'this function creates a promise that depends on another promise (until we hit a max) + doWork = function(context) + 'if we hit the max, resolve the promise and unravel the entire stack + if context.currentCount > 10000 + return promises.resolve(true) + end if + context.currentCount = context.currentCount + 1 + + 'return a promise that depends on another future promise + return promises.onThen(promises.resolve(true), function(result, context) + doWork = context.doWork + return doWork(context) + end function, context) + end function + + promises.chain(promises.resolve(true), { + currentCount: 0, + doWork: doWork + }).then(function(result, context) + doWork = context.doWork + return doWork(context) + end function).then(function(result, context) + m.testSuite.done() + end function).catch(function(error, context) + print "error", error, FormatJson(error.backtrace) + end function).toPromise() + + end function + end class end namespace function createNode(nodeType = "Node" as string, fields = {} as dynamic) as object - node = createObject("roSGNode", nodeType) - node.update(fields, true) - return node + node = createObject("roSGNode", nodeType) + node.update(fields, true) + return node end function function sleepPromise(duration = 0.0001 as float) as dynamic - promise = promises.create() - promises.internal.delay(sub(promise as dynamic) - promises.resolve(true, promise) - end sub, promise, duration) - return promise + promise = promises.create() + promises.internal.delay(sub(promise as dynamic) + promises.resolve(true, promise) + 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 + 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 From 970eb1fa83dbb530ff8f80e305d366fcded31cb2 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 19 Dec 2025 16:35:19 -0500 Subject: [PATCH 2/2] Refactor processPromiseListener to be a loop that tries combinations of args --- src/source/promises.bs | 199 +++++++++++++++++++----------------- src/source/promises.spec.bs | 3 - 2 files changed, 104 insertions(+), 98 deletions(-) diff --git a/src/source/promises.bs b/src/source/promises.bs index 6c80cd7..61a4d25 100644 --- a/src/source/promises.bs +++ b/src/source/promises.bs @@ -542,6 +542,12 @@ namespace promises.internal promiseResultLowerCase = "promiseresult" end enum + enum ListenerType + then = "then" + catch = "catch" + finally = "finally" + end enum + ' Clear storage for a given promise sub clearPromiseStorage(promise as object, nodeEvent = invalid as dynamic) if nodeEvent <> invalid then @@ -641,6 +647,7 @@ namespace promises.internal print errorMessage #end if throw errorMessage + 'bs:disable-next-line return invalid end function @@ -668,17 +675,17 @@ namespace promises.internal 'handle .then() listeners for each listener in promiseStorage.thenListeners - promises.internal.processPromiseListener(originalPromise, promiseState, listener, promiseState = promises.PromiseState.resolved, true, promiseResult) + promises.internal.processPromiseListener(originalPromise, promiseState, listener, promises.internal.ListenerType.then, promiseResult) end for 'handle .catch() listeners for each listener in promiseStorage.catchListeners - promises.internal.processPromiseListener(originalPromise, promiseState, listener, promiseState = promises.PromiseState.rejected, true, promiseResult) + promises.internal.processPromiseListener(originalPromise, promiseState, listener, promises.internal.ListenerType.catch, promiseResult) end for 'handle .finally() listeners for each listener in promiseStorage.finallyListeners - promises.internal.processPromiseListener(originalPromise, promiseState, listener, true, false, promiseResult) + promises.internal.processPromiseListener(originalPromise, promiseState, listener, promises.internal.ListenerType.finally, promiseResult) end for #if PROMISES_DEBUG if m.__promises__debug = invalid then @@ -715,8 +722,18 @@ namespace promises.internal return (type(value) = "String" or type(value) = "roString") and value <> "" end function + enum ArgCombination + none = 0 + resultOnly = 1 + resultAndContext = 2 + contextOnly = 3 + end enum + + syntax error to prevent roku from running the code, cuz this is broken! + ' Handle an individual promise listener - sub processPromiseListener(originalPromise as object, originalPromiseState as string, storageItem as object, callCallback as boolean, isThenOrCatch as boolean, promiseValue = invalid as dynamic) + sub processPromiseListener(originalPromise as object, originalPromiseState as string, storageItem as object, listenerType as promises.internal.ListenerType, promiseValue = invalid as dynamic) + newPromise = storageItem.promise #if PROMISES_DEBUG print "[promises.notify]", originalPromise.id, "notifying", newPromise.id @@ -724,104 +741,96 @@ namespace promises.internal callback = storageItem.callback context = storageItem.context hasContext = promises.internal.isSet(context) - 'only call the callback if configured to do so + 'if we don't have a context, set it to a default value that might be used for recovery + if hasContext = false then + context = 0 ' 0 works for numbers, boolean, object, dynamic. (does not work for string or function, but those are uncommon) + end if + + if listenerType = promises.internal.ListenerType.then then + callCallback = originalPromiseState = promises.PromiseState.resolved + if hasContext then + argFlows = [ArgCombination.resultAndContext, ArgCombination.resultOnly, ArgCombination.none] + else + argFlows = [ArgCombination.resultOnly, ArgCombination.resultAndContext, ArgCombination.none] + end if + else if listenerType = promises.internal.ListenerType.catch then + callCallback = originalPromiseState = promises.PromiseState.rejected + if hasContext then + argFlows = [ArgCombination.resultAndContext, ArgCombination.resultOnly, ArgCombination.none] + else + argFlows = [ArgCombination.resultOnly, ArgCombination.resultAndContext, ArgCombination.none] + end if + else 'listenerType === promises.internal.ListenerType.finally + callCallback = true + if hasContext then + argFlows = [ArgCombination.contextOnly, ArgCombination.none] + else + argFlows = [ArgCombination.none, ArgCombination.contextOnly] + end if + end if + + callbackResult = invalid + if callCallback then #if PROMISES_DEBUG print "[promises.notify]", originalPromise.id, "calling callback for", newPromise.id #end if - try - '.then and .catch take one or two parameters (`promiseValue` and optional `context`) - if isThenOrCatch then - if hasContext then - lineNumber = -1 - try - lineNumber = LINE_NUM + 1 - callbackResult = callback(promiseValue, context) - catch error - file = error.backtrace.peek() - if error.number = 241 and file.filename = promises.internal.getLibPath() and file.line_number = lineNumber then - #if PROMISES_SAVE_LISTENER_REGISTRATION_LOCATION - print "[promises.error]: " promises.internal.formatStackTrace(storageItem.registrationLocation, promises.internal.wrongNumberOfParametersMessage) - #else - print "[promises.error]: " promises.internal.formatStackTrace(error, promises.internal.wrongNumberOfParametersMessage) - #end if - callbackResult = callback(promiseValue) - else - promises.internal.logCrashIfEnabled(error) - callbackResult = promises.reject(error) - end if - end try - else - try - lineNumber = LINE_NUM + 1 - callbackResult = callback(promiseValue) - catch error - file = error.backtrace.peek() - if error.number = 241 and file.filename = promises.internal.getLibPath() and file.line_number = lineNumber then - #if PROMISES_SAVE_LISTENER_REGISTRATION_LOCATION - print "[promises.error]: " promises.internal.formatStackTrace(storageItem.registrationLocation, promises.internal.wrongNumberOfParametersMessage) - #else - print "[promises.error]: " promises.internal.formatStackTrace(error, promises.internal.wrongNumberOfParametersMessage) - #end if - callbackResult = callback(promiseValue, 0) ' 0 works for numbers, boolean, object, dynamic. (does not work for string or function, but those are uncommon) - else - promises.internal.logCrashIfEnabled(error) - callbackResult = promises.reject(error) - end if - end try - end if - '.finally callback takes 1 optional parameter (`context`) - else - if hasContext then - lineNumber = -1 - try - lineNumber = LINE_NUM + 1 - callbackResult = callback(context) - catch error - file = error.backtrace.peek() - if error.number = 241 and file.filename = promises.internal.getLibPath() and file.line_number = lineNumber then - #if PROMISES_SAVE_LISTENER_REGISTRATION_LOCATION - print "[promises.error]: " promises.internal.formatStackTrace(storageItem.registrationLocation, promises.internal.wrongNumberOfParametersMessage) - #else - print "[promises.error]: " promises.internal.formatStackTrace(error, promises.internal.wrongNumberOfParametersMessage) - #end if - callbackResult = callback() - else - promises.internal.logCrashIfEnabled(error) - callbackResult = promises.reject(error) - end if - end try + lastSeenError = invalid + ' try different arg combinations until one works. We'll try the most likely combinations first. These are defined above based on + ' whether context is provided and which type of listener this is. Sometimes callbacks might accidentally define a param when they shouldn't, + ' other times they forgot to define a param but it's required. + for each argFlow in argFlows + lineNumber = -1 + try + if argFlow = ArgCombination.resultAndContext then + lineNumber = LINE_NUM + 1 + callbackResult = callback(promiseValue, context) + + else if argFlow = ArgCombination.resultOnly then + lineNumber = LINE_NUM + 1 + callbackResult = callback(promiseValue) + + else if argFlow = ArgCombination.contextOnly then + lineNumber = LINE_NUM + 1 + callbackResult = callback(context) + + else ' if argFlow = ArgCombination.none then + lineNumber = LINE_NUM + 1 + callbackResult = callback() + end if + 'we got a result. clear the error + lastSeenError = invalid + exit for + catch error + file = error.backtrace.peek() + lastSeenError = error + if error.number = 241 and file.filename = promises.internal.getLibPath() and file.line_number = lineNumber then + wrongNumberOfParametersMessage = "Wrong number of parameters defined for promise '" + listenerType + "' callback. We will attempt to recover, but performance will suffer if you don't fix the problem." + #if PROMISES_SAVE_LISTENER_REGISTRATION_LOCATION + print "[promises.error]: " promises.internal.formatStackTrace(storageItem.registrationLocation, wrongNumberOfParametersMessage) + #else + print "[promises.error]: " promises.internal.formatStackTrace(error, wrongNumberOfParametersMessage) + #end if else - try - lineNumber = LINE_NUM + 1 - callbackResult = callback() - catch error - file = error.backtrace.peek() - if error.number = 241 and file.filename = promises.internal.getLibPath() and file.line_number = lineNumber then - #if PROMISES_SAVE_LISTENER_REGISTRATION_LOCATION - print "[promises.error]: " promises.internal.formatStackTrace(storageItem.registrationLocation, promises.internal.wrongNumberOfParametersMessage) - #else - print "[promises.error]: " promises.internal.formatStackTrace(error, promises.internal.wrongNumberOfParametersMessage) - #end if - callbackResult = callback(0) ' 0 works for numbers, boolean, object, dynamic. (does not work for string or function, but those are uncommon) - else - promises.internal.logCrashIfEnabled(error) - callbackResult = promises.reject(error) - end if - end try + exit for ' some other error occurred, exit the loop and reject below end if - end if - catch e + end try + end for + + if lastSeenError <> invalid then + 'if we get here and we still have an error, it means none of the arg combinations worked. Reject the error and let's move on. #if PROMISES_DEBUG print "[promises.notify]", originalPromise.id, "callback for", newPromise.id, "threw exception", e #end if - promises.internal.logCrashIfEnabled(e) + promises.internal.logCrashIfEnabled(lastSeenError) 'the result is a rejected promise - callbackResult = promises.reject(e) - end try - else - 'use the current promise value to pass to the next promise (this is a .catch handler) + callbackResult = promises.reject(lastSeenError) + end if + + + else 'if callCallback = false + 'pass the promise value to the next promise (this is a .catch handler) if originalPromiseState = promises.PromiseState.rejected then callbackResult = promises.reject(promiseValue) else @@ -829,7 +838,8 @@ namespace promises.internal end if end if - if isThenOrCatch then + 'is `then` or `catch` listener + if not listenerType <> promises.internal.ListenerType.then then 'if the .then() callback returned a promise. wait for it to resolve and THEN resolve the newPromise if promises.isPromise(callbackResult) then callbackPromise = callbackResult @@ -857,7 +867,8 @@ namespace promises.internal else promises.resolve(callbackResult, newPromise) end if - else + + else ' if listenerType = promises.internal.ListenerType.finally then ' This is a .finally() block if promises.isPromise(callbackResult) then callbackPromise = callbackResult @@ -1119,8 +1130,6 @@ namespace promises.internal return result end function - const wrongNumberOfParametersMessage = "Wrong number of parameters in promise callback. We have recovered, but this should be fixed as performance will suffer." - ' Returns a string representation of the stack trace ' example: ' Error: some error diff --git a/src/source/promises.spec.bs b/src/source/promises.spec.bs index bfe5726..b8b1382 100644 --- a/src/source/promises.spec.bs +++ b/src/source/promises.spec.bs @@ -1290,7 +1290,6 @@ namespace tests end sub @async - @only() @it("recovers when did NOT send context, but DID define the parameter (testing multiple param types") sub _() getGlobalAA().myCount = 0 @@ -1370,7 +1369,6 @@ namespace tests end sub @async - @only() @it("recovers when did NOT send context, but DID define the parameter (testing multiple param types") sub _() getGlobalAA().myCount = 0 @@ -1454,7 +1452,6 @@ namespace tests end sub @async - @only() @it("recovers when did NOT send context, but DID define the parameter (testing multiple param types") sub _() getGlobalAA().myCount = 0