/// import assert = require('assert'); import SyncTasks = require('../SyncTasks'); describe('SyncTasks', function () { function noop() {/*noop*/} // Amount of time to wait to ensure all sync and trivially async (e.g. setTimeout(..., 0)) things have finished. // Useful to do something 'later'. const waitTime = 25; it('Simple - null resolve after then', (done) => { const task = SyncTasks.Defer(); task.promise().then(val => { assert.equal(val, null); done(); }, err => { assert(false); }); task.resolve(null); }); it('Simple - null then after resolve', (done) => { const task = SyncTasks.Defer(); task.resolve(null); task.promise().then(val => { assert.equal(val, null); done(); }, err => { assert(false); }); }); it('Simple - reject', (done) => { const task = SyncTasks.Defer(); task.reject(2); task.promise().then(val => { assert(false); }, err => { assert.equal(err, 2); done(); }); }); it('Chain from success to success with value', (done) => { const task = SyncTasks.Defer(); task.promise().then(val => { assert.equal(val, 3); return 4; }, err => { assert(false); return null; }).then(val => { assert.equal(val, 4); done(); }, err => { assert(false); }); task.resolve(3); }); it('Chain from error to success with value', (done) => { const task = SyncTasks.Defer(); task.promise().then(val => { assert(false); return -1; }, err => { assert.equal(err, 2); return 4; }).then(val => { assert.equal(val, 4); done(); }, err => { assert(false); }); task.reject(2); }); it('Chain from success to success with promise', (done) => { const task = SyncTasks.Defer(); task.promise().then(val => { assert.equal(val, 3); return SyncTasks.Resolved(4); }, err => { assert(false); return -1; }).then(val => { assert.equal(val, 4); done(); }, err => { assert(false); }); task.resolve(3); }); it('Chain from error to success with promise', (done) => { const task = SyncTasks.Defer(); task.promise().then(val => { assert(false); return -1; }, err => { assert.equal(err, 3); return SyncTasks.Resolved(4); }).then(val => { assert.equal(val, 4); done(); }, err => { assert(false); }); task.reject(3); }); it('Chain from success to error with promise', (done) => { const task = SyncTasks.Defer(); task.promise().then(val => { assert.equal(val, 2); return SyncTasks.Rejected(4); }, err => { assert(false); return -1; }).then(val => { assert(false); }, err => { assert.equal(err, 4); done(); }); task.resolve(2); }); it('Chain from error to error with promise', (done) => { const task = SyncTasks.Defer(); task.promise().then(val => { assert(false); return -1; }, err => { assert.equal(err, 2); return SyncTasks.Rejected(4); }).then(val => { assert(false); }, err => { assert.equal(err, 4); done(); }); task.reject(2); }); it('Chain from success to promise to success with promise', (done) => { const task = SyncTasks.Defer(); task.promise().then(val => { assert.equal(val, 3); const itask = SyncTasks.Resolved(4); return itask.then(val2 => { assert.equal(val2, 4, 'inner'); return 5; }); }, err => { assert(false); return 2; }).then(val => { assert.equal(val, 5, 'outer'); done(); }, err => { assert(false); }); task.resolve(3); }); it('Exception in success to error', (done) => { const task = SyncTasks.Defer(); SyncTasks.config.exceptionsToConsole = false; task.promise().then(val => { const blah: any = null; blah.blowup(); }, err => { assert(false); }).then(val => { assert(false); }, err => { SyncTasks.config.exceptionsToConsole = true; done(); }); task.resolve(3); }); it('Exception in error to error', (done) => { const task = SyncTasks.Defer(); SyncTasks.config.exceptionsToConsole = false; task.promise().then(val => { assert(false); }, err => { const blah: any = null; blah.blowup(); }).then(val => { assert(false); }, err => { SyncTasks.config.exceptionsToConsole = true; done(); }); task.reject(3); }); it('"done" basic', (done) => { const task = SyncTasks.Defer(); task.promise().then(val => { return 4; }, err => { assert(false); return -1; }).done(val => { assert.equal(val, 4); return 2; // should be ignored }).then(val => { assert.equal(val, 4); done(); }, err => { assert(false); }); task.resolve(3); }); it('"done" does not chain', (done) => { const task = SyncTasks.Defer(); const innertask = SyncTasks.Defer(); let innerFinished = false; task.promise().then(val => { return 4; }, err => { assert(false); return -1; }).done(val => { assert.equal(val, 4); return innertask.promise().then(() => { innerFinished = true; return 2; // should be ignored }); }).then(val => { assert(!innerFinished); assert.equal(val, 4); done(); }, err => { assert(false); }); task.resolve(3); innertask.resolve(1); }); it('Finally basic', (done) => { const task = SyncTasks.Defer(); task.promise().then(val => { return 4; }, err => { assert(false); return -1; }).finally(val => { assert.equal(val, 4); return 2; // should be ignored }).then(val => { assert.equal(val, 4); done(); }, err => { assert(false); }); task.resolve(3); }); it('Finally does not chain', (done) => { const task = SyncTasks.Defer(); const innertask = SyncTasks.Defer(); let innerFinished = false; task.promise().then(val => { return 4; }, err => { assert(false); return -1; }).finally(val => { assert.equal(val, 4); return innertask.promise().then(() => { innerFinished = true; return 2; // should be ignored }); }).then(val => { assert(!innerFinished); assert.equal(val, 4); done(); }, err => { assert(false); }); task.resolve(3); innertask.resolve(1); }); it('"all" basic success', (done) => { const task = SyncTasks.Defer(); const task2 = SyncTasks.Defer(); const task3 = SyncTasks.Defer(); const task4 = SyncTasks.Defer(); SyncTasks.all([task.promise(), task2.promise(), task3.promise(), task4.promise()]).then(rets => { assert.equal(rets.length, 4); assert.equal(rets[0], 1); assert.equal(rets[1], 2); assert.equal(rets[2], 3); assert.equal(rets[3], 4); done(); }, err => { assert(false); }); task.resolve(1); task2.resolve(2); task3.resolve(3); task4.resolve(4); }); it('"all" basic failure', (done) => { const task = SyncTasks.Defer(); const task2 = SyncTasks.Defer(); SyncTasks.all([task.promise(), task2.promise()]).then(rets => { assert(false); }, err => { done(); }); task.resolve(1); task2.reject(2); }); it('"all" zero tasks', (done) => { SyncTasks.all([]).then(rets => { assert.equal(rets.length, 0); done(); }, err => { assert(false); }); }); it('"all" single null task', (done) => { SyncTasks.all([null]).then(rets => { assert.equal(rets.length, 1); done(); }, err => { assert(false); }); }); it('"all" tasks and nulls', (done) => { const task = SyncTasks.Defer(); SyncTasks.all([null, task.promise()]).then(rets => { assert.equal(rets.length, 2); done(); }, err => { assert(false); }); task.resolve(1); }); it('"race" basic success', (done) => { const task = SyncTasks.Defer(); const task2 = SyncTasks.Defer(); const task3 = SyncTasks.Defer(); const task4 = SyncTasks.Defer(); SyncTasks.race([task.promise(), task2.promise(), task3.promise(), task4.promise()]).then(ret => { assert.equal(ret, 1); done(); }, err => { assert(false); }); task.resolve(1); task2.resolve(2); task3.resolve(3); task4.resolve(4); }); it('"race" basic failure', (done) => { const task = SyncTasks.Defer(); const task2 = SyncTasks.Defer(); SyncTasks.race([task.promise(), task2.promise()]).then(ret => { assert(false); }, err => { assert.equal(err, 1); done(); }); task.reject(1); task2.resolve(2); }); it('"race" zero tasks', (done) => { SyncTasks.race([]).then(ret => { assert(false); }, err => { assert(false); }); setTimeout(() => done(), 20); }); it('"race" single null task', (done) => { SyncTasks.race([null]).then(ret => { assert.equal(ret, null); done(); }, err => { assert(false); }); }); it('"race" tasks and nulls', (done) => { const task = SyncTasks.Defer(); SyncTasks.race([null, task.promise()]).then(ret => { assert.equal(ret, null); done(); }, err => { assert(false); }); task.resolve(2); }); it('Callbacks resolve synchronously', (done) => { const task = SyncTasks.Defer(); let resolvedCount = 0; task.promise().then(() => { ++resolvedCount; }, err => { assert(false); }); task.resolve(1); assert(resolvedCount === 1); done(); }); it('Callbacks resolve in order added', (done) => { const task = SyncTasks.Defer(); let resolvedCount = 0; task.promise().then(() => { assert(resolvedCount === 0); ++resolvedCount; }, err => { assert(false); }); task.promise().then(() => { assert(resolvedCount === 1); ++resolvedCount; }, err => { assert(false); }); task.resolve(1); assert(resolvedCount === 2); done(); }); it('Failure callbacks resolve in order added', (done) => { const task = SyncTasks.Defer(); let rejectedCount = 0; task.promise().then(() => { assert(false); }, err => { assert(rejectedCount === 0); ++rejectedCount; }); task.promise().then(() => { assert(false); }, err => { assert(rejectedCount === 1); ++rejectedCount; }); task.reject(1); assert(rejectedCount === 2); done(); }); it('"unhandledErrorHandler": Failure without any callback', (done) => { let unhandledErrorHandlerCalled = false; const oldUnhandledErrorHandler = SyncTasks.config.unhandledErrorHandler; SyncTasks.config.unhandledErrorHandler = () => { unhandledErrorHandlerCalled = true; }; SyncTasks.Rejected(); setTimeout(() => { SyncTasks.config.unhandledErrorHandler = oldUnhandledErrorHandler; assert(unhandledErrorHandlerCalled); done(); }, 20); }); it('"unhandledErrorHandler": Failure with only success callback', (done) => { let unhandledErrorHandlerCalled = false; const oldUnhandledErrorHandler = SyncTasks.config.unhandledErrorHandler; SyncTasks.config.unhandledErrorHandler = () => { unhandledErrorHandlerCalled = true; }; SyncTasks.Rejected().then(() => { assert(false); }); setTimeout(() => { SyncTasks.config.unhandledErrorHandler = oldUnhandledErrorHandler; assert(unhandledErrorHandlerCalled); done(); }, 20); }); it('"unhandledErrorHandler": Failure with success callback with failure callback', (done) => { let catchBlockReached = false; SyncTasks.Rejected().then(() => { assert(false); }).catch(() => { catchBlockReached = true; }); setTimeout(() => { assert(catchBlockReached); done(); }, 20); }); it('"unhandledErrorHandler": Success to inner failure without any callback', (done) => { let unhandledErrorHandlerCalled = false; const oldUnhandledErrorHandler = SyncTasks.config.unhandledErrorHandler; SyncTasks.config.unhandledErrorHandler = () => { unhandledErrorHandlerCalled = true; }; SyncTasks.Resolved(4).then(() => { return SyncTasks.Rejected(); }); setTimeout(() => { SyncTasks.config.unhandledErrorHandler = oldUnhandledErrorHandler; assert(unhandledErrorHandlerCalled); done(); }, 20); }); it('"unhandledErrorHandler": Failure to inner failure without any callback', (done) => { let unhandledErrorHandlerCalled = 0; const oldUnhandledErrorHandler = SyncTasks.config.unhandledErrorHandler; SyncTasks.config.unhandledErrorHandler = (n: number) => { unhandledErrorHandlerCalled = n; }; SyncTasks.Rejected(1).catch(() => { return SyncTasks.Rejected(2); }); // Note: the outer "catch" has no failure handling so the inner error leaks out. setTimeout(() => { SyncTasks.config.unhandledErrorHandler = oldUnhandledErrorHandler; assert.equal(unhandledErrorHandlerCalled, 2); done(); }, 20); }); it('"unhandledErrorHandler": Each chained promise must handle', (done) => { let unhandledErrorHandlerCalled = false; let catchBlockReached = false; const oldUnhandledErrorHandler = SyncTasks.config.unhandledErrorHandler; SyncTasks.config.unhandledErrorHandler = () => { unhandledErrorHandlerCalled = true; }; const task = SyncTasks.Rejected(); task.catch(() => { catchBlockReached = true; }); // Does not handle failure. task.then(() => { assert(false); }); setTimeout(() => { SyncTasks.config.unhandledErrorHandler = oldUnhandledErrorHandler; assert(unhandledErrorHandlerCalled); assert(catchBlockReached); done(); }, 20); }); it('"unhandledErrorHandler": "fail" never "handles" the failure', (done) => { let unhandledErrorHandlerCalled = false; let failBlockReached = false; const oldUnhandledErrorHandler = SyncTasks.config.unhandledErrorHandler; SyncTasks.config.unhandledErrorHandler = () => { unhandledErrorHandlerCalled = true; }; SyncTasks.Rejected().fail(() => { failBlockReached = true; // If this was .catch, it would resolve the promise (with undefined) and the failure would be handled. }); setTimeout(() => { SyncTasks.config.unhandledErrorHandler = oldUnhandledErrorHandler; assert(unhandledErrorHandlerCalled); assert(failBlockReached); done(); }, 20); }); it('"unhandledErrorHandler": "done" does not create another "unhandled"', (done) => { let unhandledErrorHandlerCalled = false; let catchBlockReached = false; const oldUnhandledErrorHandler = SyncTasks.config.unhandledErrorHandler; SyncTasks.config.unhandledErrorHandler = () => { unhandledErrorHandlerCalled = true; }; SyncTasks.Rejected().done(() => { // Should not create a separate "unhandled" error since there is no way to "handle" it from here. // The existing "unhandled" error should continue to be "unhandled", as other tests have verified. }).catch(() => { // "Handle" the failure. catchBlockReached = true; }); setTimeout(() => { SyncTasks.config.unhandledErrorHandler = oldUnhandledErrorHandler; assert(!unhandledErrorHandlerCalled); assert(catchBlockReached); done(); }, 20); }); it('"unhandledErrorHandler": "fail" does not create another "unhandled"', (done) => { let unhandledErrorHandlerCalled = false; let catchBlockReached = false; const oldUnhandledErrorHandler = SyncTasks.config.unhandledErrorHandler; SyncTasks.config.unhandledErrorHandler = () => { unhandledErrorHandlerCalled = true; }; SyncTasks.Rejected().fail(() => { // Should not create a separate "unhandled" error since there is no way to "handle" it from here. // The existing "unhandled" error should continue to be "unhandled", as other tests have verified. }).catch(() => { // "Handle" the failure. catchBlockReached = true; }); setTimeout(() => { SyncTasks.config.unhandledErrorHandler = oldUnhandledErrorHandler; assert(!unhandledErrorHandlerCalled); assert(catchBlockReached); done(); }, 20); }); it('Add callback while resolving', (done) => { const task = SyncTasks.Defer(); const promise = task.promise(); let resolvedCount = 0; const innerTask1 = SyncTasks.Defer(); const innerTask2 = SyncTasks.Defer(); promise.then(() => { // While resolving: add callback to same promise. promise.then(() => { innerTask2.resolve(++resolvedCount); }, err => { assert(false); }); // This line should be reached before innerTask2 resolves. innerTask1.resolve(++resolvedCount); }, err => { assert(false); }); task.resolve(1); SyncTasks.all([innerTask1.promise(), innerTask2.promise()]).then(rets => { assert(rets.length === 2); assert(rets[0] === 1); assert(rets[1] === 2); done(); }, err => { assert(false); }); }); it('Add callback while rejecting', (done) => { const task = SyncTasks.Defer(); const promise = task.promise(); let rejectedCount = 0; const innerTask1 = SyncTasks.Defer(); const innerTask2 = SyncTasks.Defer(); promise.then(() => { assert(false); }, err => { // While resolving: add callback to same promise. promise.then(() => { assert(false); }, err => { innerTask2.resolve(++rejectedCount); }); // This line should be reached before innerTask2 resolves. innerTask1.resolve(++rejectedCount); }); task.reject(1); SyncTasks.all([innerTask1.promise(), innerTask2.promise()]).then(rets => { assert(rets.length === 2); assert(rets[0] === 1); assert(rets[1] === 2); done(); }, err => { assert(false); }); }); it('Cancel task (happy path)', () => { let canceled = false; let cancelContext: any; const task = SyncTasks.Defer(); const promise = task.promise(); task.onCancel((context) => { canceled = true; cancelContext = context; task.reject(5); }); promise.cancel(4); // Check the cancel caused rejection. return promise.then(() => { assert(false); return SyncTasks.Rejected(); }, (err) => { assert.equal(err, 5); assert(canceled); assert.equal(cancelContext, 4); return SyncTasks.Resolved(1); }); }); it('Cancel chain cancels task (bubble up)', () => { let canceled = false; let cancelContext: any; const task = SyncTasks.Defer(); const promise = task.promise(); task.onCancel((context) => { canceled = true; cancelContext = context; task.reject(5); }); const chain = promise.then(() => { assert(false); return SyncTasks.Rejected(); }, (err) => { assert.equal(err, 5); assert(canceled); assert.equal(cancelContext, 4); return -1; }); chain.cancel(4); // Check the chain cancel caused task rejection. return promise.then(() => { assert(false); return SyncTasks.Rejected(); }, (err) => { assert.equal(err, 5); assert(canceled); assert.equal(cancelContext, 4); return SyncTasks.Resolved(1); }); }); it('Cancel deep chain cancels task (bubble up)', () => { let canceled = false; let cancelContext: any; const task = SyncTasks.Defer(); const promise = task.promise(); task.onCancel((context) => { canceled = true; cancelContext = context; task.reject(5); }); const chain = promise.then(() => { assert(false); return SyncTasks.Rejected(); }, (err) => { assert.equal(err, 5); assert(canceled); assert.equal(cancelContext, 4); return -1; }) // Make the chain longer to further separate the cancel from the task. .always(noop) .always(noop) .always(noop); chain.cancel(4); // Check the chain cancel caused task rejection. return promise.then(() => { assert(false); return SyncTasks.Rejected(); }, (err) => { assert.equal(err, 5); assert(canceled); assert.equal(cancelContext, 4); return SyncTasks.Resolved(1); }); }); it('Cancel finished task does not call cancellation handlers', () => { const task = SyncTasks.Defer(); const promise = task.promise(); task.onCancel((context) => { assert(false); }); task.resolve(2); promise.cancel(4); }); it('Cancel finished task does not cancel inner', () => { const task = SyncTasks.Defer(); const promise = task.promise(); task.onCancel((context) => { assert(false); }); promise.then(() => { const inner = SyncTasks.Defer(); inner.onCancel((context) => { assert(false); }); return inner.promise(); }); task.resolve(2); promise.cancel(4); }); it('Cancel task then resolve during cancellation then does not call further handlers', () => { let canceled = false; const task = SyncTasks.Defer(); const promise = task.promise(); task.onCancel(() => { canceled = true; }); task.onCancel(() => { task.resolve(2); }); task.onCancel(() => { assert(false); }); promise.cancel(); assert(canceled); // Check the onCancel caused task resolution. return promise; }); it('Cancel task then reject during cancellation then does not call further handlers', () => { let canceled = false; const task = SyncTasks.Defer(); const promise = task.promise(); task.onCancel(() => { canceled = true; }); task.onCancel(() => { task.reject(5); }); task.onCancel(() => { assert(false); }); promise.cancel(); assert(canceled); // Check the onCancel caused task rejection. return promise.then(() => { assert(false); return SyncTasks.Rejected(); }, (err) => { assert.equal(err, 5); return SyncTasks.Resolved(2); }); }); it('Cancel inner does not cancel root task (no bubble out)', () => { let canceled = false; let cancelContext: any; const task = SyncTasks.Defer(); const promise = task.promise(); task.onCancel((context) => { assert(false); }); const inner = SyncTasks.Defer(); inner.onCancel((context) => { canceled = true; cancelContext = context; inner.reject(5); }); const innerPromise = inner.promise(); const chain = promise.then(() => { return innerPromise; }, (err) => { assert(false); return SyncTasks.Rejected(); }).catch(err => { assert.equal(err, 5); assert(canceled); assert.equal(cancelContext, 4); return SyncTasks.Resolved(2); }); innerPromise.cancel(4); task.resolve(2); return chain; }); it('Cancel chain cancels inner task (bubble in), with task resolved late', () => { let canceled = false; let cancelContext: any; const task = SyncTasks.Defer(); const promise = task.promise(); const chain = promise.then(() => { const inner = SyncTasks.Defer(); inner.onCancel((context) => { canceled = true; cancelContext = context; inner.reject(5); }); return inner.promise(); }, (err) => { assert(false); return SyncTasks.Rejected(); }).catch(err => { assert.equal(err, 5); assert(canceled); assert.equal(cancelContext, 4); return SyncTasks.Resolved(2); }); chain.cancel(4); task.resolve(1); return chain; }); it('Cancel chain cancels inner task (bubble in), with task resolved early', () => { let canceled = false; let cancelContext: any; const task = SyncTasks.Defer(); const promise = task.promise(); const chain = promise.then(() => { const inner = SyncTasks.Defer(); inner.onCancel((context) => { canceled = true; cancelContext = context; inner.reject(5); }); setTimeout(() => { chain.cancel(4); }, waitTime); return inner.promise(); }, (err) => { assert(false); return SyncTasks.Rejected(); }).catch(err => { assert.equal(err, 5); assert(canceled); assert.equal(cancelContext, 4); return SyncTasks.Resolved(2); }); task.resolve(1); return chain; }); it('Cancel chain cancels inner chained task, with task resolved late', () => { let canceled = false; let cancelContext: any; const task = SyncTasks.Defer(); const promise = task.promise(); const chain = promise.then(() => { const inner = SyncTasks.Defer(); inner.onCancel((context) => { canceled = true; cancelContext = context; inner.reject(5); }); return inner.promise().then(() => { // Chain another promise in place to make sure it works its way up to inner at some point. return 6; }); }, (err) => { assert(false); return SyncTasks.Rejected(); }).catch(err => { assert.equal(err, 5); assert(canceled); assert.equal(cancelContext, 4); return SyncTasks.Resolved(2); }); chain.cancel(4); task.resolve(1); return chain; }); it('Cancel chain cancels inner chained task, with task resolved early', () => { let canceled = false; let cancelContext: any; const task = SyncTasks.Defer(); const promise = task.promise(); const ret = promise.then(() => { const newTask = SyncTasks.Defer(); newTask.onCancel((context) => { canceled = true; cancelContext = context; newTask.reject(5); }); setTimeout(() => { ret.cancel(4); }, waitTime); return newTask.promise().then(() => { // Chain another promise in place to make sure it works its way up to the newTask at some point. return 6; }); }, (err) => { assert(false); return SyncTasks.Rejected(); }).catch(err => { assert.equal(err, 5); assert(canceled); assert.equal(cancelContext, 4); return SyncTasks.Resolved(2); }); task.resolve(1); return ret; }); it('Cancel .all task cancels array of tasks', () => { let canceled1 = false; let canceled2 = false; const task1 = SyncTasks.Defer(); const promise1 = task1.promise(); task1.onCancel((context) => { canceled1 = true; assert.equal(context, 4); }); const task2 = SyncTasks.Defer(); const promise2 = task2.promise(); task2.onCancel((context) => { canceled2 = true; assert.equal(context, 4); }); const allPromise = SyncTasks.all([promise1, promise2]); allPromise.cancel(4); assert(canceled1); assert(canceled2); }); it('Cancel chain from .all cancels array of tasks', () => { let canceled1 = false; const task1 = SyncTasks.Defer(); const promise1 = task1.promise(); task1.onCancel((context) => { canceled1 = true; assert.equal(context, 4); }); const chain = SyncTasks.all([promise1]) .always(noop); chain.cancel(4); assert(canceled1); }); it('Cancel .race task cancels array of tasks', () => { let canceled1 = false; let canceled2 = false; const task1 = SyncTasks.Defer(); const promise1 = task1.promise(); task1.onCancel((context) => { canceled1 = true; assert.equal(context, 4); }); const task2 = SyncTasks.Defer(); const promise2 = task2.promise(); task2.onCancel((context) => { canceled2 = true; assert.equal(context, 4); }); const allPromise = SyncTasks.race([promise1, promise2]); allPromise.cancel(4); assert(canceled1); assert(canceled2); }); it('Cancel chain from .race cancels array of task', () => { let canceled1 = false; const task1 = SyncTasks.Defer(); const promise1 = task1.promise(); task1.onCancel((context) => { canceled1 = true; assert.equal(context, 4); }); const chain = SyncTasks.race([promise1]) .always(noop); chain.cancel(4); assert(canceled1); }); it('Cancel chain cancels inner chained .all task, with task resolved late', () => { let canceled = false; let cancelContext: any; const task = SyncTasks.Defer(); const promise = task.promise(); const ret = promise.then(() => { const newTask = SyncTasks.Defer(); newTask.onCancel((context) => { canceled = true; cancelContext = context; newTask.reject(5); }); return SyncTasks.all([newTask.promise()]).then(() => { // Chain another promise in place to make sure it works its way up to the newTask at some point. return 6; }); }, (err) => { assert(false); return SyncTasks.Rejected(); }).catch(err => { assert.equal(err, 5); assert(canceled); assert.equal(cancelContext, 4); return SyncTasks.Resolved(2); }); ret.cancel(4); task.resolve(1); return ret; }); it('Cancel chain cancels inner chained .all task, with task resolved early', () => { let canceled = false; let cancelContext: any; const task = SyncTasks.Defer(); const promise = task.promise(); const ret = promise.then(() => { const newTask = SyncTasks.Defer(); newTask.onCancel((context) => { canceled = true; cancelContext = context; newTask.reject(5); }); setTimeout(() => { ret.cancel(4); }, waitTime); return SyncTasks.all([newTask.promise()]).then(() => { // Chain another promise in place to make sure it works its way up to the newTask at some point. return 6; }); }, (err) => { assert(false); return SyncTasks.Rejected(); }).catch(err => { assert.equal(err, 5); assert(canceled); assert.equal(cancelContext, 4); return SyncTasks.Resolved(2); }); task.resolve(1); return ret; }); it('Cancel shared task does not cancel children (no bubble down)', () => { const task = SyncTasks.Defer(); const promise = task.promise(); const inner1 = SyncTasks.Defer(); inner1.onCancel((context) => { assert(false); }); const inner2 = SyncTasks.Defer(); inner2.onCancel((context) => { assert(false); }); promise.then(() => inner1.promise()); promise.then(() => inner2.promise()); promise.cancel(4); task.resolve(1); }); it('Cancel chain of shared task does not cancel other chain (no bubble across)', () => { let canceled = false; let cancelContext: any; const task = SyncTasks.Defer(); const promise = task.promise(); const inner1 = SyncTasks.Defer(); inner1.onCancel((context) => { canceled = true; cancelContext = context; }); const inner2 = SyncTasks.Defer(); inner2.onCancel((context) => { assert(false); }); const chain1 = promise.then(() => inner1.promise()); promise.then(() => inner2.promise()); chain1.cancel(4); task.resolve(1); assert(canceled); assert.equal(cancelContext, 4); }); it('Cancel throws for "double cancel"', () => { const oldCatchExceptions = SyncTasks.config.catchExceptions; SyncTasks.config.catchExceptions = false; const promise = SyncTasks.Defer().promise(); promise.cancel(); try { promise.cancel(); assert.ok(false); } catch (e) { // Expected. SyncTasks.config.catchExceptions = oldCatchExceptions; } }); it('Cancel does not throw for "double cancel" due to bubble up', () => { const oldCatchExceptions = SyncTasks.config.catchExceptions; SyncTasks.config.catchExceptions = false; let countCancels = 0; const task = SyncTasks.Defer(); task.onCancel(context => { countCancels++; }); const root = task.promise(); const promise1 = root.then(noop); const promise2 = root.then(noop); try { promise1.cancel(); promise2.cancel(); SyncTasks.config.catchExceptions = oldCatchExceptions; } catch (e) { assert.ok(false); } // Make sure the root's cancel was called, but we should not be called back more than once. assert.equal(countCancels, 1); }); it('Cancel does not throw for "double cancel" due to bubble in', () => { const oldCatchExceptions = SyncTasks.config.catchExceptions; SyncTasks.config.catchExceptions = false; const task = SyncTasks.Defer(); const promise = task.promise(); const chain = promise.then(() => { const inner = SyncTasks.Defer(); const innerPromise = inner.promise(); innerPromise.cancel(); return innerPromise; }); try { chain.cancel(32); task.resolve(1); SyncTasks.config.catchExceptions = oldCatchExceptions; } catch (e) { assert.ok(false); } }); it('Cancel .all does not throw for "double cancel" due to bubble up', () => { const oldCatchExceptions = SyncTasks.config.catchExceptions; SyncTasks.config.catchExceptions = false; const promise1 = SyncTasks.Defer().promise(); const promise2 = SyncTasks.Defer().promise(); const sink = SyncTasks.all([promise1, promise2]); try { sink.cancel(); promise1.cancel(); SyncTasks.config.catchExceptions = oldCatchExceptions; } catch (e) { assert.ok(false); } }); it('Cancel .all does not throw for "double cancel" due to bubble up for already canceled promise', () => { const oldCatchExceptions = SyncTasks.config.catchExceptions; SyncTasks.config.catchExceptions = false; const promise1 = SyncTasks.Defer().promise(); const promise2 = SyncTasks.Defer().promise(); const sink = SyncTasks.all([promise1, promise2]); try { promise1.cancel(); sink.cancel(); SyncTasks.config.catchExceptions = oldCatchExceptions; } catch (e) { assert.ok(false); } }); it('Cancel .race does not throw for "double cancel" due to bubble up', () => { const oldCatchExceptions = SyncTasks.config.catchExceptions; SyncTasks.config.catchExceptions = false; const promise1 = SyncTasks.Defer().promise(); const promise2 = SyncTasks.Defer().promise(); const sink = SyncTasks.race([promise1, promise2]); try { sink.cancel(); promise1.cancel(); SyncTasks.config.catchExceptions = oldCatchExceptions; } catch (e) { assert.ok(false); } }); it('Cancel .race does not throw for "double cancel" due to bubble up for already canceled promise', () => { const oldCatchExceptions = SyncTasks.config.catchExceptions; SyncTasks.config.catchExceptions = false; const promise1 = SyncTasks.Defer().promise(); const promise2 = SyncTasks.Defer().promise(); const sink = SyncTasks.race([promise1, promise2]); try { promise1.cancel(); sink.cancel(); SyncTasks.config.catchExceptions = oldCatchExceptions; } catch (e) { assert.ok(false); } }); it('Cancel resolved promise does not call cancellation handlers', () => { const defer = SyncTasks.Defer(); const promise = defer.promise(); defer.onCancel(() => { assert(false, 'Handler should not be called'); }); defer.resolve(void 0); promise.cancel(); }); it('Multiple bubble promise cancellation results in single cancel handler callbacks', () => { const defer = SyncTasks.Defer(); const promise1 = defer.promise().then(() => { /* noop */}); const promise2 = defer.promise().then(() => { /* noop */}); let callbackCount = 0; defer.onCancel(() => { callbackCount++; }); promise1.cancel(); promise2.cancel(); assert.equal(callbackCount, 1, 'onCancel handler not called correct number of times'); }); it('deferCallback', (done) => { let got = false; let got2 = false; SyncTasks.asyncCallback(() => { got = true; }); setTimeout(() => { assert(got); assert(got2); done(); }, 1); SyncTasks.asyncCallback(() => { got2 = true; }); assert(!got); assert(!got2); }); it('thenDeferred Simple', (done) => { const task = SyncTasks.Defer(); let tooEarly = true; task.promise().then(val => { assert.equal(val, 1); return 2; }, err => { assert(false); return null; }).thenAsync(val => { assert.equal(val, 2); assert(!tooEarly); done(); }, err => { assert(false); }); SyncTasks.asyncCallback(() => { tooEarly = false; }); task.resolve(1); assert(tooEarly); }); it('thenDeferred Failure', (done) => { const task = SyncTasks.Defer(); let tooEarly = true; task.promise().then(val => { assert.equal(val, 1); return SyncTasks.Rejected(4); }, err => { assert(false); return 5; }).thenAsync(val => { assert(false); }, err => { assert.equal(err, 4); assert(!tooEarly); done(); }); SyncTasks.asyncCallback(() => { tooEarly = false; }); task.resolve(1); assert(tooEarly); }); it('toEs6Promise Simple', (done) => { const task = SyncTasks.Defer(); let tooEarly = true; task.promise().toEs6Promise().then(val => { assert.equal(val, 3.50); done(); }, err => { assert(false); }); SyncTasks.asyncCallback(() => { tooEarly = false; }); task.resolve(3.50); assert(tooEarly); }); it('toEs6Promise Resolved', (done) => { const resolved = SyncTasks.Resolved(42); let tooEarly = true; resolved.toEs6Promise().then(val => { assert.equal(val, 42); assert(tooEarly); done(); }, err => { assert(false); }); SyncTasks.asyncCallback(() => { tooEarly = false; }); assert(tooEarly); }); it('toEs6Promise Rejected', (done) => { const rejected = SyncTasks.Rejected(42); let tooEarly = true; rejected.toEs6Promise().then(val => { assert(false); }, err => { assert.equal(err, 42); assert(tooEarly); done(); }); SyncTasks.asyncCallback(() => { tooEarly = false; }); assert(tooEarly); }); it('toEs6Promise and back', (done) => { const task = SyncTasks.Defer(); const stPromise = task.promise(); const esPromise = stPromise.toEs6Promise(); const stPromiseAgain = SyncTasks.fromThenable(esPromise); stPromiseAgain.then(val => { assert.equal(val, 100500); done(); }, err => { assert(false); }); task.resolve(100500); }); });