diff --git a/package.json b/package.json index 8dfe39d4..e0e22c2a 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,11 @@ "prepublish": "npm run build", "prettier:rc": "prettier --write", "prettier": "prettier --write .", + "release:major:nc": "node scripts.js prepare_release major --no-commit", "release:major": "node scripts.js prepare_release major", + "release:minor:nc": "node scripts.js prepare_release minor --no-commit", "release:minor": "node scripts.js prepare_release minor", + "release:patch:nc": "node scripts.js prepare_release patch --no-commit", "release:patch": "node scripts.js prepare_release patch", "test:e2e": "concurrently 'npm run test:serve' 'wait-on http://localhost:3000/esm.html && npm run cypress' --kill-others --success first", "test:serve": "node tests/esm.server.js", diff --git a/scripts.js b/scripts.js index 36a681bf..8bd16c5b 100644 --- a/scripts.js +++ b/scripts.js @@ -63,13 +63,15 @@ ${commits} ${changelog}`; await fs.writeFile('CHANGELOG.md', changelog, { encoding: 'utf8' }); - await exec('git add CHANGELOG.md'); - await exec( - `git commit -m "[RELEASE] update changelog for v${newVersion}" --no-verify` - ); - await exec( - `npm version --no-commit-hooks ${newVersion} -m '[RELEASE] v${newVersion}'` - ); + if (!process.argv.includes('--no-commit')) { + await exec('git add CHANGELOG.md'); + await exec( + `git commit -m "[RELEASE] update changelog for v${newVersion}" --no-verify` + ); + await exec( + `npm version --no-commit-hooks ${newVersion} -m '[RELEASE] v${newVersion}'` + ); + } } runScript().catch((err) => { diff --git a/src/CancelablePromise.js b/src/CancelablePromise.js index 07fe48f2..2d704e4e 100644 --- a/src/CancelablePromise.js +++ b/src/CancelablePromise.js @@ -2,7 +2,14 @@ function createCallback(onResult, options) { if (onResult) { return (arg) => { if (!options.isCanceled) { - return onResult(arg); + const result = onResult(arg); + if (result && typeof result.cancel === 'function') { + if (!options.onCancelList) { + options.onCancelList = []; + } + options.onCancelList.push(result.cancel); + } + return result; } return arg; }; diff --git a/tests/CancelablePromise.test.js b/tests/CancelablePromise.test.js index 9acb256f..0fc3dbf9 100644 --- a/tests/CancelablePromise.test.js +++ b/tests/CancelablePromise.test.js @@ -278,3 +278,206 @@ describe('CancelablePromise.race()', () => { expect(callback).toHaveBeenCalledTimes(0); }); }); + +describe('Cancelable promises returned by executors', () => { + async function worflow({ withClass, withFail, withCatch }) { + const callback = jest.fn(); + let promise1; + + if (withClass) { + promise1 = new CancelablePromise((resolve, reject, onCancel) => { + callback('start p1'); + const timer = setTimeout(() => { + callback('resolve p1'); + if (withFail) { + reject(); + } else { + resolve(); + } + }, 5); + const abort = () => { + callback('abort p1'); + clearTimeout(timer); + }; + onCancel(abort); + }); + } else { + promise1 = cancelable( + new Promise((resolve, reject) => { + callback('start p1'); + delay(5, () => { + callback('resolve p1'); + if (withFail) { + reject(); + } else { + resolve(); + } + }); + }) + ); + } + + let promise2 = promise1.then( + ...[ + () => { + callback('then p2'); + const promise3 = new CancelablePromise( + (resolve, reject, onCancel) => { + callback('start p3'); + const timer = setTimeout(() => { + callback('resolve p3'); + resolve(); + }, 10); + const abort = () => { + callback('abort p3'); + clearTimeout(timer); + }; + onCancel(abort); + } + ); + return promise3; + }, + !withCatch && + (() => { + callback('error p2'); + const promise3 = new CancelablePromise( + (resolve, reject, onCancel) => { + callback('start p3'); + const timer = setTimeout(() => { + callback('resolve p3'); + resolve(); + }, 10); + const abort = () => { + callback('abort p3'); + clearTimeout(timer); + }; + onCancel(abort); + } + ); + return promise3; + }), + ].filter(Boolean) + ); + + if (withCatch) { + promise2 = promise2.catch(() => { + callback('catch p2'); + const promise3 = new CancelablePromise((resolve, reject, onCancel) => { + callback('start p3'); + const timer = setTimeout(() => { + callback('resolve p3'); + resolve(); + }, 10); + const abort = () => { + callback('abort p3'); + clearTimeout(timer); + }; + onCancel(abort); + }); + return promise3; + }); + } + + promise2.then(() => { + callback('then done'); + }); + + await delay(10, () => { + callback('cancel p2'); + promise2.cancel(); + }); + await delay(20); + return callback; + } + + it('should be canceled when fulfilled (with CancelablePromise)', async () => { + const callback = await worflow({ withClass: true, withFail: false }); + expect(callback.mock.calls).toEqual([ + ['start p1'], + ['resolve p1'], + ['then p2'], + ['start p3'], + ['cancel p2'], + ['abort p1'], + ['abort p3'], + ]); + }); + + it('should be canceled when rejected (with CancelablePromise)', async () => { + const callback = await worflow({ + withClass: true, + withFail: true, + withCatch: false, + }); + expect(callback.mock.calls).toEqual([ + ['start p1'], + ['resolve p1'], + ['error p2'], + ['start p3'], + ['cancel p2'], + ['abort p1'], + ['abort p3'], + ]); + }); + + it('should be canceled when rejected and caught (with CancelablePromise)', async () => { + const callback = await worflow({ + withClass: true, + withFail: true, + withCatch: true, + }); + expect(callback.mock.calls).toEqual([ + ['start p1'], + ['resolve p1'], + ['catch p2'], + ['start p3'], + ['cancel p2'], + ['abort p1'], + ['abort p3'], + ]); + }); + + it('should be canceled when fulfilled (with cancelable)', async () => { + const callback = await worflow({ withClass: false, withFail: false }); + expect(callback.mock.calls).toEqual([ + ['start p1'], + ['resolve p1'], + ['then p2'], + ['start p3'], + ['cancel p2'], + ['abort p3'], + ]); + }); + + it('should be canceled when rejected (with cancelable)', async () => { + const callback = await worflow({ + withClass: false, + withFail: true, + withCatch: false, + }); + expect(callback.mock.calls).toEqual([ + ['start p1'], + ['resolve p1'], + ['error p2'], + ['start p3'], + ['cancel p2'], + ['abort p3'], + ]); + }); + + it('should be canceled when rejected and caught (with cancelable)', async () => { + const callback = await worflow({ + withClass: false, + withFail: true, + withCatch: true, + }); + expect(callback.mock.calls).toEqual([ + ['start p1'], + ['resolve p1'], + ['catch p2'], + ['start p3'], + ['cancel p2'], + ['abort p3'], + ]); + }); +});