diff --git a/CHANGELOG.md b/CHANGELOG.md index f618caf..b826b95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,163 +1,318 @@ -# Change Log +# Changelog + All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](http://keepachangelog.com/) -and this project adheres to [Semantic Versioning](http://semver.org/). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). + +## [v0.10.8](https://github.com/DigitalBrainJS/c-promise/compare/v0.10.8...v0.10.8) + +## [v0.10.8](https://github.com/DigitalBrainJS/c-promise/compare/v0.10.7...v0.10.8) - 2020-12-13 + +### Commits + +- exported `promisify` method; [`8c9746b`](https://github.com/DigitalBrainJS/c-promise/commit/8c9746b2ba686dd7836aa6f5401297058cb3c7e2) +- Added tests for `CanceledError.rethrow` method; [`e3d9f6f`](https://github.com/DigitalBrainJS/c-promise/commit/e3d9f6f8493a237e5007bb2ecae8d0856bc24a00) +- spellcheck; [`895d0d6`](https://github.com/DigitalBrainJS/c-promise/commit/895d0d6bee1ea60acac56c00be64c92e10bde483) +- spellcheck; [`aa32a7b`](https://github.com/DigitalBrainJS/c-promise/commit/aa32a7b2b72b7d79f3257cb43a894ce34315ec1c) + +## [v0.10.7](https://github.com/DigitalBrainJS/c-promise/compare/v0.10.6...v0.10.7) - 2020-12-12 + +### Commits + +- Added promisify method; [`b07585d`](https://github.com/DigitalBrainJS/c-promise/commit/b07585d0cc14dd00767c81dd02e04b3015752287) +- Updated README; [`96ed673`](https://github.com/DigitalBrainJS/c-promise/commit/96ed67344eb89ba17c002d04d578880499b0b0e0) + +## [v0.10.6](https://github.com/DigitalBrainJS/c-promise/compare/v0.10.5...v0.10.6) - 2020-12-10 + +### Commits + +- added `weight`, `innerWeight` and `label` options for the `async` decorator; [`25a1d8d`](https://github.com/DigitalBrainJS/c-promise/commit/25a1d8d0d52c3ec9c160cb471e9f0f685ce358be) + +## [v0.10.5](https://github.com/DigitalBrainJS/c-promise/compare/v0.10.4...v0.10.5) - 2020-12-08 + +### Commits + +- Updated README; [`e9f1041`](https://github.com/DigitalBrainJS/c-promise/commit/e9f10415a4859805a3c6b1951ca8f923ea2b2170) + +## [v0.10.4](https://github.com/DigitalBrainJS/c-promise/compare/v0.10.3...v0.10.4) - 2020-12-07 + +### Commits + +- Added @progress decorator; [`b982644`](https://github.com/DigitalBrainJS/c-promise/commit/b982644bdefb428b53c976117661890a8b097fb5) +- Updated README; [`ce89805`](https://github.com/DigitalBrainJS/c-promise/commit/ce89805191c43a95451c8484c96408c15cb380d9) +- Updated README; [`9b5261e`](https://github.com/DigitalBrainJS/c-promise/commit/9b5261e57549555f5582cb3f4b7cebe5ca6dbdd9) + +## [v0.10.3](https://github.com/DigitalBrainJS/c-promise/compare/v0.10.2...v0.10.3) - 2020-12-07 + +### Commits + +- Fixed a bug with cleaning internal timer; [`b1b91a1`](https://github.com/DigitalBrainJS/c-promise/commit/b1b91a1650d30d1a4ee73ba2a6ad53caa57bba4c) +- Updated CHANGELOG.md; [`459fdbf`](https://github.com/DigitalBrainJS/c-promise/commit/459fdbf0cfef848d109e35212dd68bcd32ab6018) + +## [v0.10.2](https://github.com/DigitalBrainJS/c-promise/compare/v0.10.1...v0.10.2) - 2020-12-07 + +### Commits + +- Added @canceled decorator; [`7b53ca3`](https://github.com/DigitalBrainJS/c-promise/commit/7b53ca36faf9436dfdaadb978a2a4b6976ce300b) + +## [v0.10.1](https://github.com/DigitalBrainJS/c-promise/compare/v0.10.0...v0.10.1) - 2020-12-07 + +### Commits + +- Refactored AbortController; [`6255fb1`](https://github.com/DigitalBrainJS/c-promise/commit/6255fb18eed4d59d380ec71754bf56d519963555) +- Improved JSDoc annotation; [`5d4aafe`](https://github.com/DigitalBrainJS/c-promise/commit/5d4aafee14fac3e399c220b05b4ba0636bce7c50) +- Updated CHANGELOG.md; [`37c4829`](https://github.com/DigitalBrainJS/c-promise/commit/37c4829be34e4c384eba12a62c96230d83e1259a) + +## [v0.10.0](https://github.com/DigitalBrainJS/c-promise/compare/v0.9.1...v0.10.0) - 2020-12-06 + +### Commits + +- Added decorators support; [`ed3be00`](https://github.com/DigitalBrainJS/c-promise/commit/ed3be00e4009e8367a960d6912ce8d8029771608) +- Updated readme; [`5eb8611`](https://github.com/DigitalBrainJS/c-promise/commit/5eb861155d0f1445bdaff4512d14b744f7de996c) +- Fixed async decorator anchor in the README; [`ab1e49c`](https://github.com/DigitalBrainJS/c-promise/commit/ab1e49c375080251755a583944043843849cd7c6) + +## [v0.9.1](https://github.com/DigitalBrainJS/c-promise/compare/v0.9.0...v0.9.1) - 2020-11-30 + +### Commits + +- Added generator support for the `then` method; [`ee41529`](https://github.com/DigitalBrainJS/c-promise/commit/ee415295d127915f17833fbb18b9929adf8068e2) +- Refactored test for `CPromise.all`; [`4f28354`](https://github.com/DigitalBrainJS/c-promise/commit/4f28354ace5b0d502a45fa1eed8310c98b4cddbd) +- Updated CHANGELOG.md; [`cfbe7cb`](https://github.com/DigitalBrainJS/c-promise/commit/cfbe7cb2ec0f10843d13886ea9ad8652f81b5b35) + +## [v0.9.0](https://github.com/DigitalBrainJS/c-promise/compare/v0.8.2...v0.9.0) - 2020-11-29 + +### Commits + +- Improved cancellation logic to work properly with multi leaves chains; [`cef6c54`](https://github.com/DigitalBrainJS/c-promise/commit/cef6c544ffb0d3f81bd9113c1b5704eed621e32a) +- Updated CHANGELOG.md; [`7b2ab2c`](https://github.com/DigitalBrainJS/c-promise/commit/7b2ab2c78edaafba562fb55665f860aac535f361) + +## [v0.8.2](https://github.com/DigitalBrainJS/c-promise/compare/v0.8.1...v0.8.2) - 2020-11-28 + +### Commits + +- Improved README.md; [`f417c13`](https://github.com/DigitalBrainJS/c-promise/commit/f417c13d618b3d9b412166a1d491b912408e588d) + +## [v0.8.1](https://github.com/DigitalBrainJS/c-promise/compare/v0.8.0...v0.8.1) - 2020-11-28 + +### Commits + +- Made the promise executor optional; [`50e6649`](https://github.com/DigitalBrainJS/c-promise/commit/50e66497077c8305315efd75b52637a4489d3b14) + +## [v0.8.0](https://github.com/DigitalBrainJS/c-promise/compare/v0.7.6...v0.8.0) - 2020-11-27 + +### Commits + +- Added `canceled` method to catch CanceledError rejection; [`b5f1336`](https://github.com/DigitalBrainJS/c-promise/commit/b5f1336a42af1373c34eee35098122adb42c900e) +- Added `canceled` method to catch CanceledError rejection; [`3d87a7f`](https://github.com/DigitalBrainJS/c-promise/commit/3d87a7f31c1a95fafae2e39c1f64047e2151458f) + +## [v0.7.6](https://github.com/DigitalBrainJS/c-promise/compare/v0.7.5...v0.7.6) - 2020-11-26 + +### Commits + +- refactored allSettled; [`a7a6829`](https://github.com/DigitalBrainJS/c-promise/commit/a7a68299b748d2cf7e59b9670184635e7f8cf532) + +## [v0.7.5](https://github.com/DigitalBrainJS/c-promise/compare/v0.7.4...v0.7.5) - 2020-11-26 + +### Commits + +- Fixed allSettled bug with options resolving; [`3fc84c3`](https://github.com/DigitalBrainJS/c-promise/commit/3fc84c3b7079d7b138f41bc43ca22ee11101ad7d) + +## [v0.7.4](https://github.com/DigitalBrainJS/c-promise/compare/v0.7.3...v0.7.4) - 2020-11-26 + +### Commits + +- Improved isCanceled state for then method; [`004f032`](https://github.com/DigitalBrainJS/c-promise/commit/004f032a32b95a2b090466762da3da4d9ec03f07) + +## [v0.7.3](https://github.com/DigitalBrainJS/c-promise/compare/v0.7.2...v0.7.3) - 2020-11-25 + +### Commits + +- Fixed React example anchor; [`7716058`](https://github.com/DigitalBrainJS/c-promise/commit/77160586d0c50c3d9d1d9dfd0869dbba41c81188) + +## [v0.7.2](https://github.com/DigitalBrainJS/c-promise/compare/v0.7.1...v0.7.2) - 2020-11-25 + +### Commits + +- Added React example; [`d82c8a7`](https://github.com/DigitalBrainJS/c-promise/commit/d82c8a7a7ad2a21e58ca6c86eeac47df2d42c8fd) + +## [v0.7.1](https://github.com/DigitalBrainJS/c-promise/compare/v0.7.0...v0.7.1) - 2020-11-25 + +### Commits + +- Added listen method; [`4af990f`](https://github.com/DigitalBrainJS/c-promise/commit/4af990fb76b54b75d63beb474a7a3de41d11397d) + +## [v0.7.0](https://github.com/DigitalBrainJS/c-promise/compare/v0.6.1...v0.7.0) - 2020-11-24 + +### Commits + +- Improved signals API; [`bdddb5e`](https://github.com/DigitalBrainJS/c-promise/commit/bdddb5e55df4c6b0eb225cfe2456c04925abeb38) +- Updated CHANGELOG.md; [`c2b24f1`](https://github.com/DigitalBrainJS/c-promise/commit/c2b24f1d3da72fe91305bf3fd4f00ec964cdd29f) + +## [v0.6.1](https://github.com/DigitalBrainJS/c-promise/compare/v0.6.0...v0.6.1) - 2020-11-22 + +### Commits + +- Updated README.md; [`feabfe0`](https://github.com/DigitalBrainJS/c-promise/commit/feabfe0ee3d845670c6e54472508caf8a724211b) + +## [v0.6.0](https://github.com/DigitalBrainJS/c-promise/compare/v0.5.3...v0.6.0) - 2020-11-22 + +### Commits + +- Added signals support; [`ee002ea`](https://github.com/DigitalBrainJS/c-promise/commit/ee002ea1cd8aafeb03d5cdd39516ff538aca2112) +- Added signals support; [`25e2b0a`](https://github.com/DigitalBrainJS/c-promise/commit/25e2b0adae7c3ba92e2bf043badd76f2c73e2d24) +- Fixed jsdoc docs; [`b49520a`](https://github.com/DigitalBrainJS/c-promise/commit/b49520a6012321b346f06dbddd12e05a6d97e8eb) + +## [v0.5.3](https://github.com/DigitalBrainJS/c-promise/compare/v0.5.2...v0.5.3) - 2020-10-17 + +### Commits + +- Updated CHANGELOG.md; [`2736ac4`](https://github.com/DigitalBrainJS/c-promise/commit/2736ac4f801db9b092142ac13adea05a5b097246) +- Updated CHANGELOG.md; [`990f481`](https://github.com/DigitalBrainJS/c-promise/commit/990f481bef5f4af1b50d0dfc73c68163efb3f7ee) + +## [v0.5.2](https://github.com/DigitalBrainJS/c-promise/compare/v0.5.1...v0.5.2) - 2020-10-16 + +### Commits + +- Fixed docs; [`e75ec21`](https://github.com/DigitalBrainJS/c-promise/commit/e75ec21986ce5ae7ffb8aba1245585bcd7a321f4) +- Fixed bug with promise cancellation for `all` method [`c3ab73f`](https://github.com/DigitalBrainJS/c-promise/commit/c3ab73f41787e19a142893aa3dcd476545f1cc6b) -For changes before version 0.4.0, please see the commit history +## [v0.5.1](https://github.com/DigitalBrainJS/c-promise/compare/v0.5.0...v0.5.1) - 2020-10-14 -## [0.10.8] - 2020-12-13 +### Commits -### Added -- exported `promisify` method; -- `CanceledError.rethrow` method; +- Fixed bug with promise cancellation for `all` method [`011ff3f`](https://github.com/DigitalBrainJS/c-promise/commit/011ff3f2967d0f6d702ce23688705b0c59285431) -### Updated -- all static methods lazily bound to the constructor context; +## [v0.5.0](https://github.com/DigitalBrainJS/c-promise/compare/v0.4.2...v0.5.0) - 2020-10-13 -## [0.10.7] - 2020-12-12 +### Commits -### Added -- `promisify` method; +- Added concurrency, mapper, signatures options for `all` method; [`bcb4e63`](https://github.com/DigitalBrainJS/c-promise/commit/bcb4e63408c9caed093f433301e9ea2e89547e15) -## [0.10.6] - 2020-12-09 +## [v0.4.2](https://github.com/DigitalBrainJS/c-promise/compare/v0.4.1...v0.4.2) - 2020-09-25 -### Added -- `weight`, `innerWeight` and `label` options for the `async` decorator; +### Commits -## [0.10.5] - 2020-12-08 +- Updated README.md; [`f96a103`](https://github.com/DigitalBrainJS/c-promise/commit/f96a1034752c25694d14fbaafb00f4e7d8c98024) -## Updated -- Docs; -- JSDoc type annotations; -- innerWeight default value logic; +## [v0.4.1](https://github.com/DigitalBrainJS/c-promise/compare/v0.4.0...v0.4.1) - 2020-09-24 -## [0.10.4] - 2020-12-08 +### Commits -### Added -- `@progress` decorator; +- Updated README.md; [`55cf393`](https://github.com/DigitalBrainJS/c-promise/commit/55cf39375c467ed6fe887fb7ba94edd9e4fe46cf) -## [0.10.3] - 2020-12-08 +## [v0.4.0](https://github.com/DigitalBrainJS/c-promise/compare/v0.3.2...v0.4.0) - 2020-09-20 -## Fixed -- a bug with cleaning internal timer; +### Commits -## [0.10.2] - 2020-12-08 +- Added outer abort signal support; [`ecf8905`](https://github.com/DigitalBrainJS/c-promise/commit/ecf890509362043a790a963484291e8614f40881) +- Updated JSDoc signatures; [`cb2055c`](https://github.com/DigitalBrainJS/c-promise/commit/cb2055cec1494a62849669d806599da7f197c256) +- Added CHANGELOG.md; [`86067d3`](https://github.com/DigitalBrainJS/c-promise/commit/86067d3306101fc857a662a8d8b979293e9f3a08) +- Updated JSDoc signatures; [`bccee42`](https://github.com/DigitalBrainJS/c-promise/commit/bccee424084c9bdfd30074ee6065057c5a82d284) -### Added -- @canceled decorator +## [v0.3.2](https://github.com/DigitalBrainJS/c-promise/compare/v0.3.1...v0.3.2) - 2020-09-17 -## [0.10.1] - 2020-12-07 +### Commits -### Updated -- Renamed AbortControllerEx class back to AbortController to be recognized by third-party libraries; +- Introduced AsyncGeneratorScope class; [`2d1f427`](https://github.com/DigitalBrainJS/c-promise/commit/2d1f427f0a8ade2049f3db879cf611316d311104) -## [0.10.0] - 2020-12-07 +## [v0.3.1](https://github.com/DigitalBrainJS/c-promise/compare/v0.3.0...v0.3.1) - 2020-09-15 -### Added -- decorators support; -- nativeController option for the CPromise constructor; -- named export instead of default; -- playground for decorators; -- reason argument for the AbortController's abort method; +### Commits -### Removed -- dev bundles; +- Fixed CDN links; [`36e2c45`](https://github.com/DigitalBrainJS/c-promise/commit/36e2c454a39d8f48ce778f1f73428d5ee1efa51a) -### Updated -- cancellation mechanics; +## [v0.3.0](https://github.com/DigitalBrainJS/c-promise/compare/v0.2.1...v0.3.0) - 2020-09-15 -## [0.9.1] - 2020-11-30 +### Commits -### Added -- generator support for the `then` method; -- `CPromise.resolveGenerator` method; +- Updated README.hbs.md; [`503fc03`](https://github.com/DigitalBrainJS/c-promise/commit/503fc0358d6345d353bb83848c674fedcfe85404) +- Removed plugin-replace; [`5be940a`](https://github.com/DigitalBrainJS/c-promise/commit/5be940a9e9eea87105054de56addab7e68e72eeb) -### Updated -- refactored CPromise.from method; +## [v0.2.1](https://github.com/DigitalBrainJS/c-promise/compare/v0.2.0...v0.2.1) - 2020-09-14 -## [0.9.0] - 2020-11-29 +### Commits -### Added -- Support for correct cancellation of multi-leaves promise chains; -- `force` option for the `cancel` method; +- Fixed all and race methods to support iterable input; [`6829b41`](https://github.com/DigitalBrainJS/c-promise/commit/6829b41a2216211bfb4602c618159166c6d8d86e) -## [0.8.2] - 2020-11-28 +## [v0.2.0](https://github.com/DigitalBrainJS/c-promise/compare/v0.1.9...v0.2.0) - 2020-09-13 -### Updated -- Improved README.md; +### Commits -## [0.8.1] - 2020-11-28 +- Updated README.hbs.md; [`1a80ce5`](https://github.com/DigitalBrainJS/c-promise/commit/1a80ce57766dc42d3846c3285914a2fb6e7b943b) +- Fixed pre-commit script; [`b6439f9`](https://github.com/DigitalBrainJS/c-promise/commit/b6439f957159274094ce91754d7e8c7f1374ca56) +- Added debounce option for scope.progress; [`295d433`](https://github.com/DigitalBrainJS/c-promise/commit/295d43303a9f723eca0b3aacf5372703897fd892) +- Updated README.hbs.md; [`cdaf10a`](https://github.com/DigitalBrainJS/c-promise/commit/cdaf10a42c12781f41738622dbded2efb7ace1a9) +- Added package size badges; [`f52b1fe`](https://github.com/DigitalBrainJS/c-promise/commit/f52b1fe700861ee497fd0632a28f16a6b17afeef) +- Fixed pre-commit script; [`f0ddee7`](https://github.com/DigitalBrainJS/c-promise/commit/f0ddee719521331e3d65e84f82e06b2dbe54ca43) +- Updated package size badges; [`8eec56c`](https://github.com/DigitalBrainJS/c-promise/commit/8eec56c1ac4a7d6eaecc4aee9de20b5549bb66f1) -### Updated -- Made the promise executor optional; +## [v0.1.9](https://github.com/DigitalBrainJS/c-promise/compare/v0.1.8...v0.1.9) - 2020-09-11 -## [0.8.0] - 2020-11-27 +### Commits -### Added -- `canceled` method to catch CanceledError rejection; -- error throwing when trying to write read-only public property; +- Updated README.hbs.md; [`747d620`](https://github.com/DigitalBrainJS/c-promise/commit/747d620c153cca0cdea10f7e5c04f51e43fa2a7a) -### Updated -- Improved `isCanceled` flag processing for catch handlers +## [v0.1.8](https://github.com/DigitalBrainJS/c-promise/compare/v0.1.7...v0.1.8) - 2020-09-11 -## [0.7.1] - 2020-11-24 +### Commits -### Added -- `listen` method to listen AbortController signal; +- Added CPromise.from method; [`e384f6d`](https://github.com/DigitalBrainJS/c-promise/commit/e384f6d20ecb29c168ca21ce91dd1b0e2f539445) +- Added timeout test; [`b41a296`](https://github.com/DigitalBrainJS/c-promise/commit/b41a29607d9cce0c82ccaf0b30afe6511a4433da) -## [0.7.0] - 2020-11-24 +## [v0.1.7](https://github.com/DigitalBrainJS/c-promise/compare/v0.1.6...v0.1.7) - 2020-09-11 -### Added -- prepend option for the `on` method; -- handler param for ths `emitSignal` method; -- an example of using signals; +### Commits -### Updated -- reworked signal system; +- Fixed timeout cancellation; [`3238d79`](https://github.com/DigitalBrainJS/c-promise/commit/3238d79b89d19a768d630025a6c2298411bab044) -## [0.6.0] - 2020-11-20 +## [v0.1.6](https://github.com/DigitalBrainJS/c-promise/compare/v0.1.5...v0.1.6) - 2020-09-10 -### Added -- Added signals support; -- Added pause / resume methods; -- Added CPromise.allSettled method; -- Added `examples` folder; +### Commits -### Removed -- CPromiseScope - now every promise handler runs directly in the context of the Promise instance; +- Updated README.md; [`2fa1bc2`](https://github.com/DigitalBrainJS/c-promise/commit/2fa1bc2b63eb065207112d86131c3fd22d3c980a) -### Updated -- Refactored; +## [v0.1.5](https://github.com/DigitalBrainJS/c-promise/compare/v0.1.4...v0.1.5) - 2020-09-10 -## [0.5.3] - 2020-10-16 +### Commits -### Added -- `innerWeight` option for captureProgress method +- Fixed build script; [`47f2b17`](https://github.com/DigitalBrainJS/c-promise/commit/47f2b172bea4ec8fda8e51cb07aeb59079ea45d1) -## [0.5.2] - 2020-10-16 +## [v0.1.4](https://github.com/DigitalBrainJS/c-promise/compare/v0.1.3...v0.1.4) - 2020-09-10 -### Fixed -- README examples & jsdoc notations +### Commits -## [0.5.1] - 2020-10-14 +- Fixed prepublishOnly script; [`d1156b9`](https://github.com/DigitalBrainJS/c-promise/commit/d1156b9ea65e67f8cab9c06c4486efbc195cb91f) -### Fixed -- a bug with promise cancellation for `all` method +## [v0.1.3](https://github.com/DigitalBrainJS/c-promise/compare/v0.1.2...v0.1.3) - 2020-09-10 -## [0.5.0] - 2020-10-04 +### Commits -### Added +- Updated .npmignore; [`3ca5cd0`](https://github.com/DigitalBrainJS/c-promise/commit/3ca5cd0cfc9e7ad56d7f1896fa9fd56439b7f69d) +- Updated README.md; [`2f948dd`](https://github.com/DigitalBrainJS/c-promise/commit/2f948dde6e736fc8ecbd13a36524e55dd4f8a202) -- Concurrency, mapper, signatures options for `all` method +## [v0.1.2](https://github.com/DigitalBrainJS/c-promise/compare/v0.1.1...v0.1.2) - 2020-09-10 -### Removed +### Commits -- AsyncGeneratorScope class -- Resolving numbers to a delay for `CPromise.from` method +- Updated README.md; Fixed coveralls; [`323858e`](https://github.com/DigitalBrainJS/c-promise/commit/323858e6e0640ef666a6cded4c671ec051bdc82f) +- Updated README.md; [`e35d155`](https://github.com/DigitalBrainJS/c-promise/commit/e35d1554e38f272af4ac94aefeb9606da7f8c64f) +- Updated README.md; [`12318cd`](https://github.com/DigitalBrainJS/c-promise/commit/12318cdf39e2d9fe4bcd3c5f27138fe1fa970506) +- Updated README.md; [`a5c1436`](https://github.com/DigitalBrainJS/c-promise/commit/a5c1436136a96e8b67f845de96101f686afc7c12) -## [0.4.0] - 2020-09-20 +## v0.1.1 - 2020-09-10 -### Added +### Commits -- Support for an outer abort signal to cancel CPromise; +- Initial commit [`f932cb8`](https://github.com/DigitalBrainJS/c-promise/commit/f932cb8584ebd458d7961aba53f8f33c797bb650) +- Updated README.md [`a3d3f94`](https://github.com/DigitalBrainJS/c-promise/commit/a3d3f94ad8e166d6081942bca7555d00a3f6d8e9) +- Updated README.md [`7b15cb8`](https://github.com/DigitalBrainJS/c-promise/commit/7b15cb8e419624ea322c7b46bb4c6b9a05eb284a) +- Updated README.md [`f13d677`](https://github.com/DigitalBrainJS/c-promise/commit/f13d6773c7eab9004b83a46d01be80a9843c3c72) +- Updated README.md [`8bef0a5`](https://github.com/DigitalBrainJS/c-promise/commit/8bef0a5169fa2859d0102a98c4d77b8ede575e6c) +- Updated README.md [`f4e0d36`](https://github.com/DigitalBrainJS/c-promise/commit/f4e0d36d0dbae76411ed044059759d69c89a4271) +- Updated README.md [`db4b88b`](https://github.com/DigitalBrainJS/c-promise/commit/db4b88b1b9661e47f850f490fe023542e07a5e48) +- Updated README.md [`06d90b2`](https://github.com/DigitalBrainJS/c-promise/commit/06d90b2cf8771b976c40288f5bed05aca360a40b) +- Updated README.md [`4df153e`](https://github.com/DigitalBrainJS/c-promise/commit/4df153e676d0dbf42e982db7f0703b8d09f3a096) +- Updated README.md [`ebaae6b`](https://github.com/DigitalBrainJS/c-promise/commit/ebaae6bd3452c167531932e9346107ccca75517a) diff --git a/README.md b/README.md index 815a985..5eae78d 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,9 @@ - [React class component with CPromise decorators](#react-class-component-with-cpromise-decorators) - [Signals handling](#signals-handling) - [Using generators](#using-generators-as-an-alternative-of-ecma-async-functions) +- [Atomic sub-chains](#atomic-sub-chains) - [Using decorators](#using-decorators) - - [@async](#asynctimeout-number) + - [@async](#asynctimeout-number-innerweight-number-label--string-weight-number) - [@listen](#listensignal-abortsignalstringsymbol) - [@cancel](#cancelreason-string-signal-abortsignalstringsymbol) - [@canceled](#canceledonrejectederr-scope-context-function) @@ -42,19 +43,30 @@ CPromise library provides an advanced version of the built-in Promise by subclassing. You might be interested in using it if you need: -- cancel the promise (including nested) -- cancel async tasks inside React components -- some way to make cancellation declarative (with decorators) -- capture promise progress -- pause the promise +- cancel the promise through rejection (including nested) +- cancel async tasks inside React components (useful to cancel internal code when components unmounts) + - with built-in decorators for class components + - with `useAsyncEffect`&`useAsyncCallback` hooks provided by [useAsyncEffect2](https://www.npmjs.com/package/use-async-effect2) package +- cancel network requests with promises, which allows the network request to be automatically aborted when the parent async function is canceled: + - `fetch` use [cp-fetch](https://www.npmjs.com/package/cp-fetch) + - `axios` use [cp-axios](https://www.npmjs.com/package/cp-axios) +- control cancellation flow to write really complicated async flows with cancelation ability. +- define atomic promise chains that cannot be canceled in the middle of execution from upper chains +- `AbortController` support for promises +- some way to make cancellation declarative (with method decorators) +- capture promise chain progress with defining progress impact for each promise (default 1) +- generators support to write a flat code in the same way as `async`&`await` do, but with `CPromise` features support +- pause/resume the promise - pending timeout - concurrent limitation for `all` and `allSettled` methods with `mapper` reducer -- auto canceling internal async jobs of your React components - advanced signal communication In terms of the library **the cancellation means rejection with a special error subclass**. +[Codesandbox Live Demo](https://codesandbox.io/s/c-promise2-readme-basic1-7d8u0) ````javascript +import { CPromise } from "c-promise2"; + const promise= new CPromise((resolve, reject, {onCancel, onPause, onResume})=>{ onCancel(()=>{ //optionally some code here to abort your long-term task (abort request, stop timers etc.) @@ -72,7 +84,14 @@ console.log('isPromise:', promise instanceof Promise); // true setTimeout(()=> promise.cancel(), 1000); ```` -Using decorators in React component to manage async tasks +Log: +```` +isPromise: true +Failed: CanceledError: canceled +chain isCanceled: true +promise isCanceled: true +```` +Using decorators in React component to manage async tasks and protect from [well-known React leak error](https://stackoverflow.com/questions/32903001/react-setstate-on-unmounted-component): ````jsx export class FetchComponent extends React.Component { state = {text: ""}; @@ -140,9 +159,9 @@ The package consists of pre-built umd, cjs, mjs bundles which can be found in th - Import the library: ````javascript -import CPromise from "c-promise2"; -// const CPromise = require("c-promise2"); // using require -// import CPromise from "c-promise2/dev"; // development version +import {CPromise} from "c-promise2"; +// const {CPromise} = require("c-promise2"); // using require +// import {CPromise} from "c-promise2/dev"; // development version const chain= CPromise.delay(1000, 'It works!').then(message => console.log('Done', message)); @@ -276,7 +295,8 @@ setTimeout(()=> promise.cancel(), 1000); // you able to call cancel() at any time to cancel the entire chain at any stage // the related network request will also be aborted ```` -You can use the [cp-fetch package](https://www.npmjs.com/package/cp-fetch) which provides a CPromise wrapper for fetch API. +You can use the [cp-fetch package](https://www.npmjs.com/package/cp-fetch) which provides a ready to use +CPromise wrapper for cross-platform fetch API. - [Live browser example (jsfiddle.net)](https://jsfiddle.net/DigitalBrain/g0dv5L8c/5/) @@ -612,6 +632,107 @@ CPromise.resolve().then(function*(){ }) ```` +## Atomic sub-chains + +Sometimes you need to prevent any sub-chain from being canceled from the outside because you want to allow some +already started asynchronous procedure to be completed before closing the following promise chains. +To solve this challenge use `.atomic(["disabled"|"detached"|"await"])` method. +- `'detached'` - keep the sub-chain execution running in 'background', the main chain reject immediately +- `'await'` - wait for the sub-chain to complete and then reject the next promise in the outer chain +- `false` considering as `'disabled'` +- `true` considering as `'await'` + +Check out the difference with examples: +Normal cancellation behaviour `.atomic('disabled')` ([Demo](https://codesandbox.io/s/c-promise2-readme-not-atomic-i9pu8)): +````javascript +const p = CPromise.delay(1000, 1) + .then((v) => { + console.log("p1"); + return CPromise.delay(1000, 2); + }) + .then((v) => { + console.log("p2"); + return CPromise.delay(1000, 3); + }) + .atomic() + .then((v) => { + console.log("p3"); + return CPromise.delay(1000, 4); + }) + .then( + (value) => console.log(`Done:`, value), + (err) => console.warn(`Fail: ${err}`) + ); + +setTimeout(() => p.cancel(), 1500); +```` +output: +```` +p1 +Fail: CanceledError: canceled + +Process finished with exit code 0 +```` +`.atomic('detached')` cancellation behaviour ([Demo](https://codesandbox.io/s/c-promise2-readme-atomic-detached-nvvwo?file=/src/index.js)): +````javascript +const p = CPromise.delay(1000, 1) + .then((v) => { + console.log("p1"); + return CPromise.delay(1000, 2); + }) + .then((v) => { + console.log("p2"); + return CPromise.delay(1000, 3); + }) + .atomic('detached') + .then((v) => { + console.log("p3"); + return CPromise.delay(1000, 4); + }) + .then( + (value) => console.log(`Done:`, value), + (err) => console.warn(`Fail: ${err}`) + ); + +setTimeout(() => p.cancel(), 1500); +```` +output: +```` +p1 +Fail: CanceledError: canceled +p2 +```` +`.atomic('await')` cancellation behaviour ([Demo](https://codesandbox.io/s/c-promise2-readme-atomic-await-e9812)): +````javascript +const p = CPromise.delay(1000, 1) + .then((v) => { + console.log("p1"); + return CPromise.delay(1000, 2); + }) + .then((v) => { + console.log("p2"); + return CPromise.delay(1000, 3); + }) + .atomic() + .then((v) => { + console.log("p3"); + return CPromise.delay(1000, 4); + }) + .then( + (value) => console.log(`Done:`, value), + (err) => console.warn(`Fail: ${err}`) + ); + +setTimeout(() => p.cancel(), 1500); +```` +output: +```` +p1 +p2 +Fail: CanceledError: canceled + +Process finished with exit code 0 +```` ## Using decorators The library supports a few types of decorators to make your code cleaner. @@ -740,6 +861,7 @@ the user canceled the promise immediately after the promise was resolved, ## Related projects - [cp-axios](https://www.npmjs.com/package/cp-axios) - a simple axios wrapper that provides an advanced cancellation api - [cp-fetch](https://www.npmjs.com/package/cp-fetch) - fetch with timeouts and request cancellation +- [use-async-effect2](https://www.npmjs.com/package/use-async-effect2) - cancel async code in functional React components ## API Reference @@ -867,8 +989,9 @@ CPromise class * [.reject(err)](#module_CPromise..CPromise+reject) ⇒ CPromise * [.pause()](#module_CPromise..CPromise+pause) ⇒ Boolean * [.resume()](#module_CPromise..CPromise+resume) ⇒ Boolean + * [.atomic([type])](#module_CPromise..CPromise+atomic) ⇒ * [.cancel([reason], [force])](#module_CPromise..CPromise+cancel) - * [.emitSignal(type, [data], [handler], [validator])](#module_CPromise..CPromise+emitSignal) ⇒ Boolean + * [.emitSignal(type, [data], [handler], [locator])](#module_CPromise..CPromise+emitSignal) ⇒ Boolean * [.delay(ms)](#module_CPromise..CPromise+delay) ⇒ CPromise * [.then(onFulfilled, [onRejected])](#module_CPromise..CPromise+then) ⇒ CPromise * [.catch(onRejected, [filter])](#module_CPromise..CPromise+catch) ⇒ CPromise @@ -889,7 +1012,7 @@ CPromise class * [.allSettled(iterable, options)](#module_CPromise..CPromise.allSettled) ⇒ CPromise * [.from(thing, [options])](#module_CPromise..CPromise.from) ⇒ CPromise * [.promisify(originalFn, [options])](#module_CPromise..CPromise.promisify) ⇒ function - * [.resolveGenerator(generatorFn, [options])](#module_CPromise..CPromise.resolveGenerator) ⇒ CPromise + * [.run(generatorFn, [options])](#module_CPromise..CPromise.run) ⇒ CPromise @@ -1116,6 +1239,18 @@ Pause promise Resume promise **Kind**: instance method of [CPromise](#module_CPromise..CPromise) + + +#### cPromise.atomic([type]) ⇒ +Make promise chain atomic (non-cancellable for external signals) + +**Kind**: instance method of [CPromise](#module_CPromise..CPromise) +**Returns**: CPromise + +| Param | Type | +| --- | --- | +| [type] | number \| boolean \| "disabled" \| "detached" \| "await" | + #### cPromise.cancel([reason], [force]) @@ -1130,7 +1265,7 @@ throws the CanceledError that cause promise chain cancellation -#### cPromise.emitSignal(type, [data], [handler], [validator]) ⇒ Boolean +#### cPromise.emitSignal(type, [data], [handler], [locator]) ⇒ Boolean Emit a signal of the specific type **Kind**: instance method of [CPromise](#module_CPromise..CPromise) @@ -1140,7 +1275,7 @@ Emit a signal of the specific type | type | Signal | | [data] | \* | | [handler] | SignalHandler | -| [validator] | SignalValidator | +| [locator] | SignalLocator | @@ -1370,20 +1505,21 @@ Converts callback styled function|GeneratorFn|AsyncFn to CPromise async function | originalFn | function \| GeneratorFunction \| AsyncFunction | | [options] | PromisifyOptions \| function \| Boolean | - + -#### CPromise.resolveGenerator(generatorFn, [options]) ⇒ CPromise +#### CPromise.run(generatorFn, [options]) ⇒ CPromise Resolves the generator to an CPromise instance **Kind**: static method of [CPromise](#module_CPromise..CPromise) -| Param | Type | -| --- | --- | -| generatorFn | GeneratorFunction | -| [options] | Object | -| [options.args] | Array | -| [options.resolveSignatures] | Boolean | -| [options.context] | \* | +| Param | Type | Description | +| --- | --- | --- | +| generatorFn | GeneratorFunction | | +| [options] | Object | | +| [options.args] | Array | | +| [options.resolveSignatures] | Boolean | resolve extra signatures (like arrays with CPromise.all) | +| [options.scopeArg] | Boolean | pass the CPromise scope as the first argument to the generator function | +| [options.context] | \* | | @@ -1462,9 +1598,9 @@ If value is a number it will be considered as the value for timeout option If va | type | Signal | | scope | CPromise | - + -### CPromise~SignalValidator ⇒ Boolean +### CPromise~SignalLocator ⇒ Boolean **Kind**: inner typedef of [CPromise](#module_CPromise) **this**: {CPromise} @@ -1506,8 +1642,10 @@ If value is a number it will be considered as the value for timeout option If va | Name | Type | Description | | --- | --- | --- | -| multiArgs | Boolean | aggregate all passed arguments to an array | -| finalize | PromisifyFinalizeFn | aggregate all passed arguments to an array | +| [multiArgs] | Boolean | aggregate all passed arguments to an array | +| [finalize] | PromisifyFinalizeFn | aggregate all passed arguments to an array | +| [fnType] | "plain" \| "generator" \| "async" | | +| [scopeArg] | boolean | pass the CPromise scope as the first argument to the generator function | ## License diff --git a/jsdoc2md/README.hbs.md b/jsdoc2md/README.hbs.md index 81895f5..07600c4 100644 --- a/jsdoc2md/README.hbs.md +++ b/jsdoc2md/README.hbs.md @@ -22,8 +22,9 @@ - [React class component with CPromise decorators](#react-class-component-with-cpromise-decorators) - [Signals handling](#signals-handling) - [Using generators](#using-generators-as-an-alternative-of-ecma-async-functions) +- [Atomic sub-chains](#atomic-sub-chains) - [Using decorators](#using-decorators) - - [@async](#asynctimeout-number) + - [@async](#asynctimeout-number-innerweight-number-label--string-weight-number) - [@listen](#listensignal-abortsignalstringsymbol) - [@cancel](#cancelreason-string-signal-abortsignalstringsymbol) - [@canceled](#canceledonrejectederr-scope-context-function) @@ -42,19 +43,30 @@ CPromise library provides an advanced version of the built-in Promise by subclassing. You might be interested in using it if you need: -- cancel the promise (including nested) -- cancel async tasks inside React components -- some way to make cancellation declarative (with decorators) -- capture promise progress -- pause the promise +- cancel the promise through rejection (including nested) +- cancel async tasks inside React components (useful to cancel internal code when components unmounts) + - with built-in decorators for class components + - with `useAsyncEffect`&`useAsyncCallback` hooks provided by [useAsyncEffect2](https://www.npmjs.com/package/use-async-effect2) package +- cancel network requests with promises, which allows the network request to be automatically aborted when the parent async function is canceled: + - `fetch` use [cp-fetch](https://www.npmjs.com/package/cp-fetch) + - `axios` use [cp-axios](https://www.npmjs.com/package/cp-axios) +- control cancellation flow to write really complicated async flows with cancelation ability. +- define atomic promise chains that cannot be canceled in the middle of execution from upper chains +- `AbortController` support for promises +- some way to make cancellation declarative (with method decorators) +- capture promise chain progress with defining progress impact for each promise (default 1) +- generators support to write a flat code in the same way as `async`&`await` do, but with `CPromise` features support +- pause/resume the promise - pending timeout - concurrent limitation for `all` and `allSettled` methods with `mapper` reducer -- auto canceling internal async jobs of your React components - advanced signal communication In terms of the library **the cancellation means rejection with a special error subclass**. +[Codesandbox Live Demo](https://codesandbox.io/s/c-promise2-readme-basic1-7d8u0) ````javascript +import { CPromise } from "c-promise2"; + const promise= new CPromise((resolve, reject, {onCancel, onPause, onResume})=>{ onCancel(()=>{ //optionally some code here to abort your long-term task (abort request, stop timers etc.) @@ -72,7 +84,14 @@ console.log('isPromise:', promise instanceof Promise); // true setTimeout(()=> promise.cancel(), 1000); ```` -Using decorators in React component to manage async tasks +Log: +```` +isPromise: true +Failed: CanceledError: canceled +chain isCanceled: true +promise isCanceled: true +```` +Using decorators in React component to manage async tasks and protect from [well-known React leak error](https://stackoverflow.com/questions/32903001/react-setstate-on-unmounted-component): ````jsx export class FetchComponent extends React.Component { state = {text: ""}; @@ -140,9 +159,9 @@ The package consists of pre-built umd, cjs, mjs bundles which can be found in th - Import the library: ````javascript -import CPromise from "c-promise2"; -// const CPromise = require("c-promise2"); // using require -// import CPromise from "c-promise2/dev"; // development version +import {CPromise} from "c-promise2"; +// const {CPromise} = require("c-promise2"); // using require +// import {CPromise} from "c-promise2/dev"; // development version const chain= CPromise.delay(1000, 'It works!').then(message => console.log('Done', message)); @@ -276,7 +295,8 @@ setTimeout(()=> promise.cancel(), 1000); // you able to call cancel() at any time to cancel the entire chain at any stage // the related network request will also be aborted ```` -You can use the [cp-fetch package](https://www.npmjs.com/package/cp-fetch) which provides a CPromise wrapper for fetch API. +You can use the [cp-fetch package](https://www.npmjs.com/package/cp-fetch) which provides a ready to use +CPromise wrapper for cross-platform fetch API. - [Live browser example (jsfiddle.net)](https://jsfiddle.net/DigitalBrain/g0dv5L8c/5/) @@ -612,6 +632,107 @@ CPromise.resolve().then(function*(){ }) ```` +## Atomic sub-chains + +Sometimes you need to prevent any sub-chain from being canceled from the outside because you want to allow some +already started asynchronous procedure to be completed before closing the following promise chains. +To solve this challenge use `.atomic(["disabled"|"detached"|"await"])` method. +- `'detached'` - keep the sub-chain execution running in 'background', the main chain reject immediately +- `'await'` - wait for the sub-chain to complete and then reject the next promise in the outer chain +- `false` considering as `'disabled'` +- `true` considering as `'await'` + +Check out the difference with examples: +Normal cancellation behaviour `.atomic('disabled')` ([Demo](https://codesandbox.io/s/c-promise2-readme-not-atomic-i9pu8)): +````javascript +const p = CPromise.delay(1000, 1) + .then((v) => { + console.log("p1"); + return CPromise.delay(1000, 2); + }) + .then((v) => { + console.log("p2"); + return CPromise.delay(1000, 3); + }) + .atomic() + .then((v) => { + console.log("p3"); + return CPromise.delay(1000, 4); + }) + .then( + (value) => console.log(`Done:`, value), + (err) => console.warn(`Fail: ${err}`) + ); + +setTimeout(() => p.cancel(), 1500); +```` +output: +```` +p1 +Fail: CanceledError: canceled + +Process finished with exit code 0 +```` +`.atomic('detached')` cancellation behaviour ([Demo](https://codesandbox.io/s/c-promise2-readme-atomic-detached-nvvwo?file=/src/index.js)): +````javascript +const p = CPromise.delay(1000, 1) + .then((v) => { + console.log("p1"); + return CPromise.delay(1000, 2); + }) + .then((v) => { + console.log("p2"); + return CPromise.delay(1000, 3); + }) + .atomic('detached') + .then((v) => { + console.log("p3"); + return CPromise.delay(1000, 4); + }) + .then( + (value) => console.log(`Done:`, value), + (err) => console.warn(`Fail: ${err}`) + ); + +setTimeout(() => p.cancel(), 1500); +```` +output: +```` +p1 +Fail: CanceledError: canceled +p2 +```` +`.atomic('await')` cancellation behaviour ([Demo](https://codesandbox.io/s/c-promise2-readme-atomic-await-e9812)): +````javascript +const p = CPromise.delay(1000, 1) + .then((v) => { + console.log("p1"); + return CPromise.delay(1000, 2); + }) + .then((v) => { + console.log("p2"); + return CPromise.delay(1000, 3); + }) + .atomic() + .then((v) => { + console.log("p3"); + return CPromise.delay(1000, 4); + }) + .then( + (value) => console.log(`Done:`, value), + (err) => console.warn(`Fail: ${err}`) + ); + +setTimeout(() => p.cancel(), 1500); +```` +output: +```` +p1 +p2 +Fail: CanceledError: canceled + +Process finished with exit code 0 +```` ## Using decorators The library supports a few types of decorators to make your code cleaner. @@ -740,6 +861,7 @@ the user canceled the promise immediately after the promise was resolved, ## Related projects - [cp-axios](https://www.npmjs.com/package/cp-axios) - a simple axios wrapper that provides an advanced cancellation api - [cp-fetch](https://www.npmjs.com/package/cp-fetch) - fetch with timeouts and request cancellation +- [use-async-effect2](https://www.npmjs.com/package/use-async-effect2) - cancel async code in functional React components ## API Reference diff --git a/lib/c-promise.js b/lib/c-promise.js index 3179c8c..5c9cdb0 100644 --- a/lib/c-promise.js +++ b/lib/c-promise.js @@ -38,6 +38,27 @@ const SIGNAL_CANCEL = Symbol('SIGNAL_CANCEL'); const SIGNAL_PAUSE = Symbol('SIGNAL_PAUSE'); const SIGNAL_RESUME = Symbol('SIGNAL_RESUME'); +const ATOMIC_TYPE_DISABLED = 0; +const ATOMIC_TYPE_DETACHED = 1; +const ATOMIC_TYPE_AWAIT = 2; + +const atomicMap2= { + 'disabled': ATOMIC_TYPE_DISABLED, + 'detached': ATOMIC_TYPE_DETACHED, + 'await': ATOMIC_TYPE_AWAIT, +} + +const atomicMap= new Map([ + ['disabled', ATOMIC_TYPE_DISABLED], + ['detached', ATOMIC_TYPE_DETACHED], + ['await', ATOMIC_TYPE_AWAIT], + [false, ATOMIC_TYPE_DISABLED], + [true, ATOMIC_TYPE_AWAIT], + [ATOMIC_TYPE_DISABLED, ATOMIC_TYPE_DISABLED], + [ATOMIC_TYPE_DETACHED, ATOMIC_TYPE_DETACHED], + [ATOMIC_TYPE_AWAIT, ATOMIC_TYPE_AWAIT], +]); + const promiseAssocStore = new WeakMap(); const controllersStore = new WeakMap(); @@ -135,7 +156,9 @@ class CPromise extends Promise { label: '', weight: 1, value: undefined, - nativeController + nativeController, + atomic: ATOMIC_TYPE_DISABLED, + canceledWith: null }; this.onCancel = this.onCancel.bind(this); @@ -648,6 +671,10 @@ class CPromise extends Promise { resolve(); } + if(!isRejected && shadow.canceledWith){ + complete(shadow.canceledWith, true); + } + if (!isThenable(value)) { complete(value, isRejected); return; @@ -728,6 +755,24 @@ class CPromise extends Promise { }); } + /** + * Make promise chain atomic (non-cancellable for external signals) + * @param {number|boolean|"disabled"|"detached"|"await"} [type] + * @returns CPromise + */ + + atomic(type = ATOMIC_TYPE_AWAIT) { + const _type = atomicMap.get(type); + + if (_type === undefined) { + throw Error(`Unknown atomic type '${type}'`); + } + + this[_shadow].atomic = _type; + + return this; + } + /** * throws the CanceledError that cause promise chain cancellation * @param {String|Error} [reason] @@ -736,10 +781,25 @@ class CPromise extends Promise { cancel(reason, force = false) { return this.emitSignal(SIGNAL_CANCEL, {err: CanceledError.from(reason), force}, function ({err}) { - this.reject(err); + !this[_shadow].canceledWith && this.reject(err); return true; - }, ({force}, type, scope, isRoot) => { - return (!isRoot && !force && scope[_shadow].leafsCount > 1); + }, ({err, force}, type, scope, isRoot) => { + + const shadow = scope[_shadow]; + const {atomic}= shadow; + + if (atomic === ATOMIC_TYPE_DETACHED) { + return false; + } + + if(atomic === ATOMIC_TYPE_AWAIT){ + shadow.canceledWith= err; + return true; + } + + if (!isRoot && !force && scope[_shadow].leafsCount > 1) { + return false; + } }); } @@ -757,7 +817,7 @@ class CPromise extends Promise { */ /** - * @typedef {Function} SignalValidator + * @typedef {Function} SignalLocator * @param {*} data * @param {Signal} type * @param {CPromise} scope @@ -771,27 +831,31 @@ class CPromise extends Promise { * @param {Signal} type * @param {*} [data] * @param {SignalHandler} [handler] - * @param {SignalValidator} [validator] + * @param {SignalLocator} [locator] * @returns {Boolean} */ - emitSignal(type, data, handler, validator) { + emitSignal(type, data, handler, locator) { const emit = (scope, isRoot) => { const shadow = scope[_shadow]; if (!shadow.isPending) return false; - if (validator && validator.call(scope, data, type, scope, isRoot)) { + const locatorResult = locator ? locator.call(scope, data, type, scope, isRoot) : undefined; + + if (locatorResult === false) { return false; } - let {parent, innerChain} = shadow; + if (locatorResult !== true) { + let {parent, innerChain} = shadow; - if (parent && emit(parent, false)) { - return true; - } + if (parent && emit(parent, false)) { + return true; + } - if (innerChain && emit(innerChain, false)) { - return true; + if (innerChain && emit(innerChain, false)) { + return true; + } } return !!(scope.emitHook('signal', type, data) || handler && handler.call(scope, data, type, scope)); @@ -847,7 +911,7 @@ class CPromise extends Promise { }) : onFulfilled; return isGeneratorFunction(cb) ? - this.constructor.resolveGenerator(cb, { + this.constructor.run(cb, { resolveSignatures: true, args: [value] }) : cb.call(promise, value, promise) @@ -1301,7 +1365,7 @@ class CPromise extends Promise { } } else if (type === 'function') { if (isGeneratorFunction(thing)) { - return this.resolveGenerator(thing, args) + return this.run(thing, args) } } @@ -1319,8 +1383,10 @@ class CPromise extends Promise { /** * @typedef {Object} PromisifyOptions - * @property {Boolean} multiArgs aggregate all passed arguments to an array - * @property {PromisifyFinalizeFn} finalize aggregate all passed arguments to an array + * @property {Boolean} [multiArgs] aggregate all passed arguments to an array + * @property {PromisifyFinalizeFn} [finalize] aggregate all passed arguments to an array + * @property {"plain"|"generator"|"async"} [fnType] + * @property {boolean} [scopeArg] pass the CPromise scope as the first argument to the generator function */ /** @@ -1341,14 +1407,15 @@ class CPromise extends Promise { options !== undefined && validateOptions(options, { multiArgs: validators.boolean, finalize: validators.plainFunction, - fnType: validators.string + fnType: validators.string, + scopeArg: validators.boolean }); } - const {multiArgs, finalize} = options || {}; + const {multiArgs, finalize, fnType, scopeArg} = options || {}; const context = this; - switch (getFnType(originalFn)) { + switch (fnType || getFnType(originalFn)) { case "plain": return (...args) => { return new this((resolve, reject, scope) => { @@ -1365,8 +1432,9 @@ class CPromise extends Promise { } case "generator": return function (...args) { - return context.resolveGenerator(originalFn, { + return context.run(originalFn, { context: this, + scopeArg, args }); } @@ -1384,12 +1452,13 @@ class CPromise extends Promise { * @param {GeneratorFunction} generatorFn * @param {Object} [options] * @param {Array} [options.args] - * @param {Boolean} [options.resolveSignatures] + * @param {Boolean} [options.resolveSignatures] resolve extra signatures (like arrays with CPromise.all) + * @param {Boolean} [options.scopeArg] pass the CPromise scope as the first argument to the generator function * @param {*} [options.context] * @returns {CPromise} */ - static resolveGenerator(generatorFn, {args, resolveSignatures, context} = {}) { + static run(generatorFn, {args, resolveSignatures, context, scopeArg= false} = {}) { return new this((resolve, reject, scope) => { let generator; @@ -1397,18 +1466,22 @@ class CPromise extends Promise { context = scope; } - switch (args ? args.length : 0) { - case 0: - generator = generatorFn.call(context, scope); - break; - case 1: - generator = generatorFn.call(context, args[0], scope); - break; - case 2: - generator = generatorFn.call(context, args[0], args[1], scope); - break; - default: - generator = generatorFn.apply(context, [...args, scope]); + if(!scopeArg){ + generator = generatorFn.apply(context, args); + }else{ + switch (args ? args.length : 0) { + case 0: + generator = generatorFn.call(context, scope); + break; + case 1: + generator = generatorFn.call(context, scope, args[0]); + break; + case 2: + generator = generatorFn.call(context, scope, args[0], args[1]); + break; + default: + generator = generatorFn.apply(context, [scope, ...args]); + } } if (!isGenerator(generator)) { @@ -1738,7 +1811,7 @@ const decorators = { decorator.descriptor = { value: function (...args) { - let promise = context.resolveGenerator(originalFn, { + let promise = context.run(originalFn, { context: this, args }); diff --git a/package-lock.json b/package-lock.json index 9d63ddc..98a4b47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -780,6 +780,34 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, + "auto-changelog": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/auto-changelog/-/auto-changelog-2.2.1.tgz", + "integrity": "sha512-XlykJfZrXlWUAADBqGoN1elmntrRcx7oEymyYB3NRPEZxv0TfYHfivmwzejUMnwAdXKCgbQPo7GV5ULs3jwpfw==", + "dev": true, + "requires": { + "commander": "^5.0.0", + "handlebars": "^4.7.3", + "lodash.uniqby": "^4.7.0", + "node-fetch": "^2.6.0", + "parse-github-url": "^1.0.2", + "semver": "^6.3.0" + }, + "dependencies": { + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -2609,6 +2637,12 @@ "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", "dev": true }, + "lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=", + "dev": true + }, "log-driver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", @@ -2797,6 +2831,12 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, "node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -6688,6 +6728,12 @@ "callsites": "^3.0.0" } }, + "parse-github-url": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz", + "integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==", + "dev": true + }, "parse-json": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", diff --git a/package.json b/package.json index 61724de..64f80c8 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "coveralls": "nyc report --reporter=text-lcov | coveralls", "prepublishOnly": "npm run build && npm run test:coverage && npm run docs", "postversion": "git push && git push --tags", + "changelog": "auto-changelog -p", + "version": "npm run changelog && git add CHANGELOG.md", "build": "rollup -c", "build:watch": "nodemon --watch lib/ --exec \\\"npm run build\\\"", "dev": "cross-env NODE_ENV=development \"npm run test:watch\"", @@ -42,6 +44,12 @@ "pre-commit": "npm run docs ; git add ." } }, + "auto-changelog": { + "output": "CHANGELOG.md", + "template": "keepachangelog", + "unreleased": true, + "commitLimit": false + }, "repository": "https://github.com/DigitalBrainJS/c-promise.git", "bugs": { "url": "https://github.com/DigitalBrainJS/c-promise/issues" @@ -119,6 +127,7 @@ "@rollup/plugin-commonjs": "^15.0.0", "@rollup/plugin-multi-entry": "^4.0.0", "@rollup/plugin-node-resolve": "^9.0.0", + "auto-changelog": "^2.2.1", "coveralls": "^3.1.0", "cross-env": "^7.0.2", "husky": "^4.3.0", diff --git a/test/tests/CPromise.js b/test/tests/CPromise.js index 646269d..88a423c 100644 --- a/test/tests/CPromise.js +++ b/test/tests/CPromise.js @@ -5,802 +5,868 @@ const {CanceledError} = CPromise; const delay = (ms, value, options) => new CPromise(resolve => setTimeout(() => resolve(value), ms), options); const makePromise = (ms, value, handler) => { - return new CPromise((resolve, reject, scope) => { - const timer = setTimeout(resolve, ms, value); - scope.onCancel(() => { - clearTimeout(timer); - handler && handler(scope); - }) - }); + return new CPromise((resolve, reject, scope) => { + const timer = setTimeout(resolve, ms, value); + scope.onCancel(() => { + clearTimeout(timer); + handler && handler(scope); + }) + }); }; module.exports = { - constructor: { - 'should create instance of CancelablePromise': function () { - const promise = new CPromise((resolve, reject) => { - }); - assert(promise instanceof CPromise); - assert(promise instanceof Promise); - } - }, + constructor: { + 'should create instance of CancelablePromise': function () { + const promise = new CPromise((resolve, reject) => { + }); + assert(promise instanceof CPromise); + assert(promise instanceof Promise); + } + }, - 'should support cancellation by the external signal': async function () { - const controller = new CPromise.AbortController(); + 'should support cancellation by the external signal': async function () { + const controller = new CPromise.AbortController(); - const timestamp = Date.now(); - const time = () => Date.now() - timestamp; + const timestamp = Date.now(); + const time = () => Date.now() - timestamp; - setTimeout(() => controller.abort(), 55); + setTimeout(() => controller.abort(), 55); - return new CPromise((resolve, reject) => { - setTimeout(resolve, 100); - }, {signal: controller.signal}).then(() => { - throw Error('not cancelled'); - }, (err) => { - if (!CPromise.isCanceledError(err)) { - if (time() < 50) { - throw Error('Early cancellation'); - } - throw err; - } - }) + return new CPromise((resolve, reject) => { + setTimeout(resolve, 100); + }, {signal: controller.signal}).then(() => { + throw Error('not cancelled'); + }, (err) => { + if (!CPromise.isCanceledError(err)) { + if (time() < 50) { + throw Error('Early cancellation'); + } + throw err; + } + }) + }, + + 'cancellation': { + 'should reject the promise with CanceledError': async function () { + const promise = new CPromise((resolve, reject) => { + setTimeout(resolve, 1000, 123); + }); + + const timestamp = Date.now(); + const timeout = 100; + + setTimeout(() => { + promise.cancel(); + }, timeout); + + await promise.then(() => { + assert.fail('promise has not been canceled'); + }, (err) => { + if (err instanceof CPromise.CanceledError) { + if (Date.now() - timestamp < timeout) { + assert.fail('early cancellation detected') + } + return; + } + throw err; + }); }, - 'cancellation': { - 'should reject the promise with CanceledError': async function () { - const promise = new CPromise((resolve, reject) => { - setTimeout(resolve, 1000, 123); - }); - - const timestamp = Date.now(); - const timeout = 100; - - setTimeout(() => { - promise.cancel(); - }, timeout); - - await promise.then(() => { - assert.fail('promise has not been canceled'); - }, (err) => { - if (err instanceof CPromise.CanceledError) { - if (Date.now() - timestamp < timeout) { - assert.fail('early cancellation detected') - } - return; - } - throw err; - }); - }, - - 'should reject the promise chain with CanceledError': async function () { - let currentChain = 1; - - const chain = delay(100) - .then(() => { - currentChain = 2 - return delay(100) - }) - .then(() => { - currentChain = 3 - return delay(100) - }) - .then(() => { - currentChain = 4 - return delay(100) - }) - .then(() => { - currentChain = 5 - }) - - const timestamp = Date.now(); - const timeout = 250; - const targetChainIndex = 3; - - setTimeout(() => { - chain.cancel(); - }, timeout); - - await chain.then(() => { - assert.fail('promise has not been canceled'); - }, (err) => { - if (err instanceof CPromise.CanceledError) { - if (Date.now() - timestamp < timeout - 5) { - assert.fail('early cancellation detected') - } - if (currentChain !== targetChainIndex) { - assert.equal(currentChain, targetChainIndex, 'wrong chain raised the error') - } - return; - } - throw err; - }); - }, - - 'throwing the CanceledError inside the promise': { - "should lead to chains cancellation": async function () { - let canceled = false; - let signaled = false; - - return CPromise.delay(10, 123).then((value, {signal, onCancel}) => { - onCancel((reason) => { - assert.equal(reason.message, 'test'); - canceled = true; - }); - - signal.addEventListener('abort', () => { - signaled = true; - }) - - return CPromise.delay(20).then(() => { - throw new CPromise.CanceledError('test'); - }); - }).then(() => { - assert.fail("has not been rejected"); - }, (err) => { - assert.equal(err.message, 'test'); - assert.ok(canceled, "not cancelled"); - assert.ok(signaled, "not signaled"); - assert.ok(err instanceof CPromise.CanceledError); - }) - } - }, + 'should reject the promise chain with CanceledError': async function () { + let currentChain = 1; - 'should cancel only isolated leaves': async function () { - let rootCanceled = false; - let firstCanceled = false; - let secondCanceled = false; - - const root = makePromise(1000, null, () => { - rootCanceled = true; - }); - - const firstLeaf = root.then(() => makePromise(1000)).then(() => { - assert.fail('first promise leaf was not canceled'); - }).canceled(() => { - firstCanceled = true; - }); - - const secondLeaf = root.then(() => makePromise(1000)).then(() => { - assert.fail('second promise leaf was not canceled'); - }).canceled(() => { - secondCanceled = true; - }); - - firstLeaf.cancel(); - await firstLeaf; - assert.equal(firstCanceled, true); - assert.equal(secondCanceled, false); - assert.equal(rootCanceled, false); - secondLeaf.cancel(); - await secondLeaf; - assert.equal(firstCanceled, true); - assert.equal(secondCanceled, true); - assert.equal(rootCanceled, true); - await root.canceled(); - }, + const chain = delay(100) + .then(() => { + currentChain = 2 + return delay(100) + }) + .then(() => { + currentChain = 3 + return delay(100) + }) + .then(() => { + currentChain = 4 + return delay(100) + }) + .then(() => { + currentChain = 5 + }) - 'should cancel all leaves if the force option is set to true': async function () { - let rootCanceled = false; - let firstCanceled = false; - let secondCanceled = false; - - const root = makePromise(1000, null, () => { - rootCanceled = true; - }); - - const firstLeaf = root.then(() => makePromise(1000)).then(() => { - assert.fail('first promise leaf was not canceled'); - }).canceled(() => { - firstCanceled = true; - }); - - const secondLeaf = root.then(() => makePromise(1000)).then(() => { - assert.fail('second promise leaf was not canceled'); - }).canceled(() => { - secondCanceled = true; - }); - - firstLeaf.cancel('', true); - await firstLeaf; - assert.equal(firstCanceled, true); - assert.equal(secondCanceled, true); - assert.equal(rootCanceled, true); + const timestamp = Date.now(); + const timeout = 250; + const targetChainIndex = 3; + + setTimeout(() => { + chain.cancel(); + }, timeout); + + await chain.then(() => { + assert.fail('promise has not been canceled'); + }, (err) => { + if (err instanceof CPromise.CanceledError) { + if (Date.now() - timestamp < timeout - 5) { + assert.fail('early cancellation detected') + } + if (currentChain !== targetChainIndex) { + assert.equal(currentChain, targetChainIndex, 'wrong chain raised the error') + } + return; } + throw err; + }); }, - 'progress capturing': { - 'should return correct chain progress': async function () { - const chain = delay(100) - .then(() => { - assertProgress(0.2) - return delay(100) - }) - .then(() => { - assertProgress(0.4) - return delay(100) - }) - .then(() => { - assertProgress(0.6) - return delay(100) - }) - .then(() => { - assertProgress(0.8) - });//.captureProgress(); - - const assertProgress = (expected) => { - assert.equal(chain.progress(), expected); - } - - assertProgress(0); - - await chain.then(() => { - assertProgress(1); - }) - } + 'throwing the CanceledError inside the promise': { + "should lead to chains cancellation": async function () { + let canceled = false; + let signaled = false; + + return CPromise.delay(10, 123).then((value, {signal, onCancel}) => { + onCancel((reason) => { + assert.equal(reason.message, 'test'); + canceled = true; + }); + + signal.addEventListener('abort', () => { + signaled = true; + }) + + return CPromise.delay(20).then(() => { + throw new CPromise.CanceledError('test'); + }); + }).then(() => { + assert.fail("has not been rejected"); + }, (err) => { + assert.equal(err.message, 'test'); + assert.ok(canceled, "not cancelled"); + assert.ok(signaled, "not signaled"); + assert.ok(err instanceof CPromise.CanceledError); + }) + } }, - 'suspension': { - 'should support pause and resume methods': async function() { - let timestamp = Date.now(); - let pauseEmitted, resumeEmitted; - const passed = () => { - return Date.now() - timestamp; - } - const chain = new CPromise((resolve, reject, {onPause, onResume}) => { - setTimeout(resolve, 500); - onPause(() => { - pauseEmitted = true; - }); - - onResume(() => { - resumeEmitted = true; - }); - }).then(() => { - assert.ok(passed() > 1200, `early completion (${passed()}ms)`); - assert.ok(pauseEmitted, 'pause event has not been emitted'); - assert.ok(resumeEmitted, 'resume event has not been emitted'); - }); - - setTimeout(() => { - chain.pause(); - setTimeout(() => { - chain.resume(); - }, 1000); - }, 300); - - return chain; - } + 'should cancel only isolated leaves': async function () { + let rootCanceled = false; + let firstCanceled = false; + let secondCanceled = false; + + const root = makePromise(1000, null, () => { + rootCanceled = true; + }); + + const firstLeaf = root.then(() => makePromise(1000)).then(() => { + assert.fail('first promise leaf was not canceled'); + }).canceled(() => { + firstCanceled = true; + }); + + const secondLeaf = root.then(() => makePromise(1000)).then(() => { + assert.fail('second promise leaf was not canceled'); + }).canceled(() => { + secondCanceled = true; + }); + + firstLeaf.cancel(); + await firstLeaf; + assert.equal(firstCanceled, true); + assert.equal(secondCanceled, false); + assert.equal(rootCanceled, false); + secondLeaf.cancel(); + await secondLeaf; + assert.equal(firstCanceled, true); + assert.equal(secondCanceled, true); + assert.equal(rootCanceled, true); + await root.canceled(); }, - 'timeout': { - 'should cancel the chain with timeout reason': async function () { - const p = new CPromise(function (resolve, reject) { - setTimeout(resolve, 1000); - }); - - return p - .timeout(100) - .then(() => { - assert.fail('chain was not cancelled'); - }, err => { - assert.ok(err instanceof CanceledError); - assert.equal(err.message, 'timeout'); - }) - } - }, + 'should cancel all leaves if the force option is set to true': async function () { + let rootCanceled = false; + let firstCanceled = false; + let secondCanceled = false; + + const root = makePromise(1000, null, () => { + rootCanceled = true; + }); + + const firstLeaf = root.then(() => makePromise(1000)).then(() => { + assert.fail('first promise leaf was not canceled'); + }).canceled(() => { + firstCanceled = true; + }); + + const secondLeaf = root.then(() => makePromise(1000)).then(() => { + assert.fail('second promise leaf was not canceled'); + }).canceled(() => { + secondCanceled = true; + }); + + firstLeaf.cancel('', true); + await firstLeaf; + assert.equal(firstCanceled, true); + assert.equal(secondCanceled, true); + assert.equal(rootCanceled, true); + } + }, + + 'progress capturing': { + 'should return correct chain progress': async function () { + const chain = delay(100) + .then(() => { + assertProgress(0.2) + return delay(100) + }) + .then(() => { + assertProgress(0.4) + return delay(100) + }) + .then(() => { + assertProgress(0.6) + return delay(100) + }) + .then(() => { + assertProgress(0.8) + });//.captureProgress(); + + const assertProgress = (expected) => { + assert.equal(chain.progress(), expected); + } + + assertProgress(0); - 'CPromise#then': { - 'should support generators': async function(){ - return CPromise.resolve() - .then(function*(){ - const result1= yield makePromise(100, 123); - const result2= yield makePromise(100, 456); - return result1 + result2; - }) - .then(function*(value){ - assert.equal(value, 123 + 456); - }) + await chain.then(() => { + assertProgress(1); + }) + } + }, + + 'suspension': { + 'should support pause and resume methods': async function () { + let timestamp = Date.now(); + let pauseEmitted, resumeEmitted; + const passed = () => { + return Date.now() - timestamp; + } + const chain = new CPromise((resolve, reject, {onPause, onResume}) => { + setTimeout(resolve, 500); + onPause(() => { + pauseEmitted = true; + }); + + onResume(() => { + resumeEmitted = true; + }); + }).then(() => { + assert.ok(passed() > 1200, `early completion (${passed()}ms)`); + assert.ok(pauseEmitted, 'pause event has not been emitted'); + assert.ok(resumeEmitted, 'resume event has not been emitted'); + }); + + setTimeout(() => { + chain.pause(); + setTimeout(() => { + chain.resume(); + }, 1000); + }, 300); + + return chain; + } + }, + + 'timeout': { + 'should cancel the chain with timeout reason': async function () { + const p = new CPromise(function (resolve, reject) { + setTimeout(resolve, 1000); + }); + + return p + .timeout(100) + .then(() => { + assert.fail('chain was not cancelled'); + }, err => { + assert.ok(err instanceof CanceledError); + assert.equal(err.message, 'timeout'); + }) + } + }, + + 'CPromise#then': { + 'should support generators': async function () { + return CPromise.resolve() + .then(function* () { + const result1 = yield makePromise(100, 123); + const result2 = yield makePromise(100, 456); + return result1 + result2; + }) + .then(function* (value) { + assert.equal(value, 123 + 456); + }) + } + }, + + 'CPromise#Symbol(toCPromise)': { + 'should be invoked to convert the object to an CPromise instance': async function () { + const toCPromise = Symbol.for('toCPromise'); + let invoked = false; + const obj = { + [toCPromise]: function (CPromise) { + invoked = true; + return new CPromise((resolve) => resolve(123)); } - }, + }; - 'CPromise#Symbol(toCPromise)': { - 'should be invoked to convert the object to an CPromise instance': async function () { - const toCPromise = Symbol.for('toCPromise'); - let invoked = false; - const obj = { - [toCPromise]: function (CPromise) { - invoked = true; - return new CPromise((resolve) => resolve(123)); - } - }; + const promise = CPromise.from(obj); - const promise = CPromise.from(obj); + assert.ok(invoked); + assert.ok(promise instanceof CPromise); - assert.ok(invoked); - assert.ok(promise instanceof CPromise); + return promise.then(value => { + assert.equal(value, 123); + }) + } + }, + + 'CPromise#setProgress()': { + 'should set the value of the promise progress': function (done) { + const p = new CPromise(function (resolve, reject) { + let progress = 0; + let i = 0; + const timer = setInterval(() => { + progress += 0.2; + this.progress(progress, {description: 'test', value: i++}); + if (progress >= 1) { + clearInterval(timer); + resolve('done'); + } + + }, 10); + }); + + const expect = [0.2, 0.4, 0.6, 0.8, 1]; + let index = 0; + + p.on('progress', (actualProgress, scope, data) => { + const expected = expect[index]; + + assert.equal(actualProgress, expected); + assert.deepStrictEqual(data, {description: 'test', value: index}); + index++; + }) + + p.then(result => { + assert.equal(result, 'done'); + done(); + }).catch(done); + } + }, + + 'CPromise#canceled()': { + 'should catch the CanceledError rejection': async function () { + let onCanceledCalled = false; + const chain = CPromise.delay(500) + .canceled(() => { + onCanceledCalled = true; + }) + .catch((err) => { + assert.fail(`should not throw: ${err}`); + }) - return promise.then(value => { - assert.equal(value, 123); - }) - } - }, + setTimeout(() => chain.cancel(), 0); - 'CPromise#setProgress()': { - 'should set the value of the promise progress': function (done) { - const p = new CPromise(function (resolve, reject) { - let progress = 0; - let i = 0; - const timer = setInterval(() => { - progress += 0.2; - this.progress(progress, {description: 'test', value: i++}); - if (progress >= 1) { - clearInterval(timer); - resolve('done'); - } - - }, 10); - }); - - const expect = [0.2, 0.4, 0.6, 0.8, 1]; - let index = 0; - - p.on('progress', (actualProgress, scope, data) => { - const expected = expect[index]; - - assert.equal(actualProgress, expected); - assert.deepStrictEqual(data, {description: 'test', value: index}); - index++; - }) + return chain.then((value) => { + assert.equal(value, undefined); + assert.equal(onCanceledCalled, true, 'onCanceledCalled was not called'); + assert.equal(chain.isCanceled, true, `isCanceled is not true`) + }); + } + }, - p.then(result => { - assert.equal(result, 'done'); - done(); - }).catch(done); + 'CPromise.from': { + 'should convert thing to a CPromise instance': async function () { + let isCanceled = false; + const thenable = { + then() { + }, + cancel() { + isCanceled = true; } - }, + } - 'CPromise#canceled()': { - 'should catch the CanceledError rejection': async function(){ - let onCanceledCalled= false; - const chain= CPromise.delay(500) - .canceled(()=>{ - onCanceledCalled= true; - }) - .catch((err) => { - assert.fail(`should not throw: ${err}`); - }) - - setTimeout(()=> chain.cancel(), 0); - - return chain.then((value)=>{ - assert.equal(value, undefined); - assert.equal(onCanceledCalled, true, 'onCanceledCalled was not called'); - assert.equal(chain.isCanceled, true, `isCanceled is not true`) - }); - } - }, + const chain = CPromise.from(thenable).then(() => { + assert.fail('not canceled'); + }, (err) => { + assert.ok(err instanceof CanceledError); + assert.ok(isCanceled) + }); - 'CPromise.from': { - 'should convert thing to a CPromise instance': async function () { - let isCanceled = false; - const thenable = { - then() { - }, - cancel() { - isCanceled = true; - } - } - - const chain = CPromise.from(thenable).then(() => { - assert.fail('not canceled'); - }, (err) => { - assert.ok(err instanceof CanceledError); - assert.ok(isCanceled) - }); - - chain.cancel(); - - return chain; - }, + chain.cancel(); - 'generator': { - 'should resolve generator to a CPromise': function () { - assert.ok(CPromise.from(function* () { - }) instanceof CPromise); - }, - 'should resolve internal chains': async function () { - const timestamp = Date.now(); - const time = () => Date.now() - timestamp; - - const delay = (ms, value) => new Promise(resolve => setTimeout(resolve, ms, value)); - - return CPromise.from(function* () { - const resolved1 = yield CPromise.delay(105, 123); - assert.ok(time() >= 100); - assert.equal(resolved1, 123); - const resolved2 = yield delay(100, 456) - assert.equal(resolved2, 456); - }); - }, - - 'should reject the promise if generator thrown an error': async function () { - const timestamp= Date.now(); - const time = () => Date.now() - timestamp; - return CPromise.from(function* () { - const timestamp = Date.now(); - const time = () => Date.now() - timestamp; - yield CPromise.delay(105); - throw Error('test'); - }).then(() => { - assert.ok(time() >= 100, 'early throw detected'); - assert.fail('the generator did not throw an error') - }, (err) => { - assert.equal(err.message, 'test'); - }) - }, - 'should support cancellation': async function () { - let thrown = false; - let canceledInternals = false; - - const delay = (ms) => new CPromise((resolve, reject, {onCancel}) => { - onCancel(() => { - canceledInternals = true; - }); - setTimeout(resolve, ms); - }); - - const chain = CPromise.from(function* () { - yield CPromise.delay(100); - try { - yield delay(100); - } catch (err) { - thrown = true; - assert.ok(err instanceof CanceledError, 'error is not an instanceof CanceledError'); - } - - yield CPromise.delay(100); - - if (!thrown) { - assert.fail('The canceled error was not thrown'); - } - }); - - setTimeout(() => { - chain.cancel(); - }, 150); - - return chain; - }, - 'should support progress capturing': async function () { - const expected = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]; - let index = 0; - - const chain = CPromise.from(function* () { - this.innerWeight(10) - let i = 10; - while (--i > 0) { - yield CPromise.delay(100); - } - }).progress(value => { - assert.equal(value, expected[index]); - index++; - }); - - return chain; - }, - - 'should proxy signals': async function(){ - const chain= CPromise.from(function*(){ - yield new CPromise((resolve, reject, scope)=>{ - scope.on('signal', (type, data)=>{ - assert.equal(type, 'test'); - assert.equal(data, 123); - resolve(); - }) - }); - }); - - setTimeout(()=>{ - chain.emitSignal('test', 123); - }, 0); - - return chain; - } - } + return chain; }, - 'CPromise.all': { - 'should be resolved with an array of inner chain values': async function () { - const v1 = 123; - const v2 = 456; - const timestamp = Date.now(); - const time = () => Date.now() - timestamp; - return CPromise.all([ - CPromise.delay(55, v1), - CPromise.delay(105, v2) - ]).then((values) => { - assert.ok(time() >= 100); - assert.deepStrictEqual(values, [v1, v2]); - }) - }, + 'generator': { + 'should resolve generator to a CPromise': function () { + assert.ok(CPromise.from(function* () { + }) instanceof CPromise); + }, + 'should resolve internal chains': async function () { + const timestamp = Date.now(); + const time = () => Date.now() - timestamp; - 'should cancel pending chains on reject': async function () { - const message = 'test'; - let canceledCounter = 0; - return CPromise.all([ - CPromise.reject(new Error(message)), - makePromise(50, 123, () => canceledCounter++), - makePromise(100, 456, () => canceledCounter++), - ]).then(() => { - assert.fail('does not throw'); - }, err => { - assert.equal(err.message, message); - }).then(() => { - assert.equal(canceledCounter, 2); - }) - }, + const delay = (ms, value) => new Promise(resolve => setTimeout(resolve, ms, value)); + + return CPromise.from(function* () { + const resolved1 = yield CPromise.delay(105, 123); + assert.ok(time() >= 100); + assert.equal(resolved1, 123); + const resolved2 = yield delay(100, 456) + assert.equal(resolved2, 456); + }); + }, - 'should support concurrency': async function () { - let pending = 0; - - return CPromise.all(function* () { - for (let i = 0; i < 5; i++) { - pending++; - yield makePromise(500, i).then((v) => { - pending--; - assert.ok(pending < 2); - return v; - }); - } - }, {concurrency: 2}).then((values) => { - assert.deepStrictEqual(values, [0, 1, 2, 3, 4]); + 'should reject the promise if generator thrown an error': async function () { + const timestamp = Date.now(); + const time = () => Date.now() - timestamp; + return CPromise.from(function* () { + const timestamp = Date.now(); + const time = () => Date.now() - timestamp; + yield CPromise.delay(105); + throw Error('test'); + }).then(() => { + assert.ok(time() >= 100, 'early throw detected'); + assert.fail('the generator did not throw an error') + }, (err) => { + assert.equal(err.message, 'test'); + }) + }, + 'should support cancellation': async function () { + let thrown = false; + let canceledInternals = false; + + const delay = (ms) => new CPromise((resolve, reject, {onCancel}) => { + onCancel(() => { + canceledInternals = true; + }); + setTimeout(resolve, ms); + }); + + const chain = CPromise.from(function* () { + yield CPromise.delay(100); + try { + yield delay(100); + } catch (err) { + thrown = true; + assert.ok(err instanceof CanceledError, 'error is not an instanceof CanceledError'); + } + + yield CPromise.delay(100); + + if (!thrown) { + assert.fail('The canceled error was not thrown'); + } + }); + + setTimeout(() => { + chain.cancel(); + }, 150); + + return chain; + }, + 'should support progress capturing': async function () { + const expected = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]; + let index = 0; + + const chain = CPromise.from(function* () { + this.innerWeight(10) + let i = 10; + while (--i > 0) { + yield CPromise.delay(100); + } + }).progress(value => { + assert.equal(value, expected[index]); + index++; + }); + + return chain; + }, + + 'should proxy signals': async function () { + const chain = CPromise.from(function* () { + yield new CPromise((resolve, reject, scope) => { + scope.on('signal', (type, data) => { + assert.equal(type, 'test'); + assert.equal(data, 123); + resolve(); }) - }, + }); + }); - 'should proxy signals': async function(){ - const chain= CPromise.all([ - new CPromise((resolve, reject, scope)=>{ - scope.on('signal', (type, data)=>{ - assert.equal(type, 'test'); - assert.equal(data, 123); - resolve(); - }) - }), - new CPromise((resolve, reject, scope)=>{ - scope.on('signal', (type, data)=>{ - assert.equal(type, 'test'); - assert.equal(data, 123); - resolve(); - }) - }) - ]); - - setTimeout(()=>{ - chain.emitSignal('test', 123); - }, 0); - - return chain; - } - }, + setTimeout(() => { + chain.emitSignal('test', 123); + }, 0); - 'CPromise.race': { - 'should return a promise that fulfills or rejects as soon as one of the promises settled': async function () { - const v1 = 123; - const v2 = 456; - const timestamp = Date.now(); - const time = () => Date.now() - timestamp; - return CPromise.race([ - CPromise.delay(55, v1), - CPromise.delay(100, v2) - ]).then((value) => { - assert.ok(time() >= 50 && time() < 100); - assert.equal(value, v1); - }) - }, + return chain; + } + } + }, + + 'CPromise.all': { + 'should be resolved with an array of inner chain values': async function () { + const v1 = 123; + const v2 = 456; + const timestamp = Date.now(); + const time = () => Date.now() - timestamp; + return CPromise.all([ + CPromise.delay(55, v1), + CPromise.delay(105, v2) + ]).then((values) => { + assert.ok(time() >= 100); + assert.deepStrictEqual(values, [v1, v2]); + }) + }, - 'should cancel other pending chains on settled': async function () { - let canceledCounter = 0; - return CPromise.race([ - makePromise(50, 123, () => canceledCounter++), - makePromise(100, 456, () => canceledCounter++), - makePromise(150, 789, () => canceledCounter++), - ]).then(() => { - assert.equal(canceledCounter, 2); - }); - }, + 'should cancel pending chains on reject': async function () { + const message = 'test'; + let canceledCounter = 0; + return CPromise.all([ + CPromise.reject(new Error(message)), + makePromise(50, 123, () => canceledCounter++), + makePromise(100, 456, () => canceledCounter++), + ]).then(() => { + assert.fail('does not throw'); + }, err => { + assert.equal(err.message, message); + }).then(() => { + assert.equal(canceledCounter, 2); + }) + }, - 'should proxy signals': async function(){ - return new Promise(resolve=>{ - let counter=0; - const handle= ()=>{ - if (++counter === 2) { - resolve(); - } - } - - const chain= CPromise.race([ - new CPromise((resolve, reject, scope)=>{ - scope.on('signal', (type, data)=>{ - assert.equal(type, 'test'); - assert.equal(data, 123); - handle(); - }) - }), - new CPromise((resolve, reject, scope)=>{ - scope.on('signal', (type, data)=>{ - assert.equal(type, 'test'); - assert.equal(data, 123); - handle(); - }) - }) - ]); - - setTimeout(()=>{ - chain.emitSignal('test', 123); - }, 0); - }); + 'should support concurrency': async function () { + let pending = 0; + + return CPromise.all(function* () { + for (let i = 0; i < 5; i++) { + pending++; + yield makePromise(500, i).then((v) => { + pending--; + assert.ok(pending < 2); + return v; + }); } + }, {concurrency: 2}).then((values) => { + assert.deepStrictEqual(values, [0, 1, 2, 3, 4]); + }) }, - 'CPromise.allSettled': async function(){ - const err= new Error('test1'); - return CPromise.allSettled([ - delay(100, 123), - CPromise.reject(err), - CPromise.resolve(456) - ]).then(results=>{ - assert.deepStrictEqual(results, [ - {status: 'fulfilled', value: 123}, - {status: 'rejected', reason: err}, - {status: 'fulfilled', value: 456} - ]); + 'should proxy signals': async function () { + const chain = CPromise.all([ + new CPromise((resolve, reject, scope) => { + scope.on('signal', (type, data) => { + assert.equal(type, 'test'); + assert.equal(data, 123); + resolve(); + }) + }), + new CPromise((resolve, reject, scope) => { + scope.on('signal', (type, data) => { + assert.equal(type, 'test'); + assert.equal(data, 123); + resolve(); + }) }) - }, + ]); - 'CPromise.on': { - 'should add new listener': function () { - const ee= new CPromise(resolve=>{}); - assert.equal(ee.listenersCount('test'), 0); - ee.on('test', function(){}); - assert.equal(ee.listenersCount('test'), 1); - ee.on('test', function(){}); - assert.equal(ee.listenersCount('test'), 2); - } + setTimeout(() => { + chain.emitSignal('test', 123); + }, 0); + + return chain; + } + }, + + 'CPromise.race': { + 'should return a promise that fulfills or rejects as soon as one of the promises settled': async function () { + const v1 = 123; + const v2 = 456; + const timestamp = Date.now(); + const time = () => Date.now() - timestamp; + return CPromise.race([ + CPromise.delay(55, v1), + CPromise.delay(100, v2) + ]).then((value) => { + assert.ok(time() >= 50 && time() < 100); + assert.equal(value, v1); + }) }, - 'CPromise.off': { - 'should remove the listener': function () { - const ee= new CPromise(resolve=>{}); - const listener1= function(){}; - const listener2= function(){}; - ee.on('test', listener1); - assert.equal(ee.listenersCount('test'), 1); - ee.on('test', listener2); - assert.equal(ee.listenersCount('test'), 2); - ee.off('test', listener1); - assert.equal(ee.listenersCount('test'), 1); - ee.off('test', listener2); - assert.equal(ee.listenersCount('test'), 0); - } + 'should cancel other pending chains on settled': async function () { + let canceledCounter = 0; + return CPromise.race([ + makePromise(50, 123, () => canceledCounter++), + makePromise(100, 456, () => canceledCounter++), + makePromise(150, 789, () => canceledCounter++), + ]).then(() => { + assert.equal(canceledCounter, 2); + }); }, - 'CPromise.emit': { - 'should emit the event listeners': function () { - const ee= new CPromise(resolve=>{}); - let invoked1, invoked2; - const listener1= function(...data){ - invoked1= true; - assert.deepStrictEqual(data, [1, 2, 3]); - }; - const listener2= function(...data){ - invoked2= true; - assert.deepStrictEqual(data, [1, 2, 3]); - }; - ee.on('test', listener1); - ee.on('test', listener2); - - ee.emit('test', 1, 2, 3); - - assert.ok(invoked1); - assert.ok(invoked2); + 'should proxy signals': async function () { + return new Promise(resolve => { + let counter = 0; + const handle = () => { + if (++counter === 2) { + resolve(); + } } - }, - 'CPromise.promisify': { - 'callback styled function': { - 'should handle resolving with a single argument': async function () { - const fn = CPromise.promisify(function (arg0, cb) { - assert.strictEqual(arg0, 123); - setTimeout(cb, 100, undefined, 456); - }); + const chain = CPromise.race([ + new CPromise((resolve, reject, scope) => { + scope.on('signal', (type, data) => { + assert.equal(type, 'test'); + assert.equal(data, 123); + handle(); + }) + }), + new CPromise((resolve, reject, scope) => { + scope.on('signal', (type, data) => { + assert.equal(type, 'test'); + assert.equal(data, 123); + handle(); + }) + }) + ]); - assert.ok(typeof fn === 'function'); + setTimeout(() => { + chain.emitSignal('test', 123); + }, 0); + }); + } + }, + + 'CPromise.allSettled': async function () { + const err = new Error('test1'); + return CPromise.allSettled([ + delay(100, 123), + CPromise.reject(err), + CPromise.resolve(456) + ]).then(results => { + assert.deepStrictEqual(results, [ + {status: 'fulfilled', value: 123}, + {status: 'rejected', reason: err}, + {status: 'fulfilled', value: 456} + ]); + }) + }, + + 'CPromise.on': { + 'should add new listener': function () { + const ee = new CPromise(resolve => { + }); + assert.equal(ee.listenersCount('test'), 0); + ee.on('test', function () { + }); + assert.equal(ee.listenersCount('test'), 1); + ee.on('test', function () { + }); + assert.equal(ee.listenersCount('test'), 2); + } + }, + + 'CPromise.off': { + 'should remove the listener': function () { + const ee = new CPromise(resolve => { + }); + const listener1 = function () { + }; + const listener2 = function () { + }; + ee.on('test', listener1); + assert.equal(ee.listenersCount('test'), 1); + ee.on('test', listener2); + assert.equal(ee.listenersCount('test'), 2); + ee.off('test', listener1); + assert.equal(ee.listenersCount('test'), 1); + ee.off('test', listener2); + assert.equal(ee.listenersCount('test'), 0); + } + }, + + 'CPromise.emit': { + 'should emit the event listeners': function () { + const ee = new CPromise(resolve => { + }); + let invoked1, invoked2; + const listener1 = function (...data) { + invoked1 = true; + assert.deepStrictEqual(data, [1, 2, 3]); + }; + const listener2 = function (...data) { + invoked2 = true; + assert.deepStrictEqual(data, [1, 2, 3]); + }; + ee.on('test', listener1); + ee.on('test', listener2); + + ee.emit('test', 1, 2, 3); + + assert.ok(invoked1); + assert.ok(invoked2); + } + }, - const promise = fn(123); + 'CPromise.promisify': { + 'callback styled function': { + 'should handle resolving with a single argument': async function () { + const fn = CPromise.promisify(function (arg0, cb) { + assert.strictEqual(arg0, 123); + setTimeout(cb, 100, undefined, 456); + }); - assert.ok(promise instanceof CPromise); + assert.ok(typeof fn === 'function'); - return promise.then(value => { - assert.strictEqual(value, 456); - }) - }, + const promise = fn(123); - 'should handle resolving with multiple arguments': async function () { - const fn = CPromise.promisify(function (arg0, cb) { - assert.strictEqual(arg0, 123); - setTimeout(cb, 100, undefined, 456, 789); - }); + assert.ok(promise instanceof CPromise); - assert.ok(typeof fn === 'function'); + return promise.then(value => { + assert.strictEqual(value, 456); + }) + }, - const promise = fn(123); + 'should handle resolving with multiple arguments': async function () { + const fn = CPromise.promisify(function (arg0, cb) { + assert.strictEqual(arg0, 123); + setTimeout(cb, 100, undefined, 456, 789); + }); - assert.ok(promise instanceof CPromise); + assert.ok(typeof fn === 'function'); - return promise.then(value => { - assert.deepStrictEqual(value, [456, 789]); - }) - }, + const promise = fn(123); - 'should handle a single argument as an array when multiArgs option activated': async function () { - const fn = CPromise.promisify(function (arg0, cb) { - assert.strictEqual(arg0, 123); - setTimeout(cb, 100, undefined, 456); - }, {multiArgs: true}); + assert.ok(promise instanceof CPromise); - assert.ok(typeof fn === 'function'); + return promise.then(value => { + assert.deepStrictEqual(value, [456, 789]); + }) + }, - const promise = fn(123); + 'should handle a single argument as an array when multiArgs option activated': async function () { + const fn = CPromise.promisify(function (arg0, cb) { + assert.strictEqual(arg0, 123); + setTimeout(cb, 100, undefined, 456); + }, {multiArgs: true}); - assert.ok(promise instanceof CPromise); + assert.ok(typeof fn === 'function'); - return promise.then(value => { - assert.deepStrictEqual(value, [456]); - }) - }, + const promise = fn(123); - 'should handle rejection': async function () { - const fn = CPromise.promisify(function (arg0, cb) { - assert.strictEqual(arg0, 123); - setTimeout(cb, 100, new Error('test')); - }); + assert.ok(promise instanceof CPromise); - const promise = fn(123); + return promise.then(value => { + assert.deepStrictEqual(value, [456]); + }) + }, - return promise.then(value => { - assert.fail(`doesn't throw`); - }).catch(err => { - assert.ok(err.message, 'test'); - }) - } - }, + 'should handle rejection': async function () { + const fn = CPromise.promisify(function (arg0, cb) { + assert.strictEqual(arg0, 123); + setTimeout(cb, 100, new Error('test')); + }); - 'should support async function decoration': async function () { - const fn = CPromise.promisify(async function (arg0) { - return delay(100, 123); - }); + const promise = fn(123); - const promise = fn(123); + return promise.then(value => { + assert.fail(`doesn't throw`); + }).catch(err => { + assert.ok(err.message, 'test'); + }) + } + }, - assert.ok(promise instanceof CPromise); + 'should support async function decoration': async function () { + const fn = CPromise.promisify(async function (arg0) { + return delay(100, 123); + }); - return promise.then(value => { - assert.deepStrictEqual(value, 123); - }) - }, + const promise = fn(123); - 'should support generator function decoration': async function () { - const fn = CPromise.promisify(function* (arg0) { - return yield delay(100, 123); - }); + assert.ok(promise instanceof CPromise); - const promise = fn(123); + return promise.then(value => { + assert.deepStrictEqual(value, 123); + }) + }, - assert.ok(promise instanceof CPromise); + 'should support generator function decoration': async function () { + const fn = CPromise.promisify(function* (arg0) { + return yield delay(100, 123); + }); - return promise.then(value => { - assert.deepStrictEqual(value, 123); - }) - } + const promise = fn(123); + + assert.ok(promise instanceof CPromise); + + return promise.then(value => { + assert.deepStrictEqual(value, 123); + }) + } + }, + + 'CPromise#atomic': { + 'atomic("detached")': { + 'should protect promise chain from canceling from outside and keep it running in the background': async () => { + let atomicChainCompleted = false; + const chain = CPromise.delay(200, 1) + .then(v => CPromise.delay(200, v + 2)) + .then(result => { + atomicChainCompleted = true; + assert.strictEqual(result, 3); + }, err => { + assert.fail(`Unexpected rejecting: ${err}`); + }) + .atomic('detached') + .then(v => CPromise.delay(1000)) + .then(v => { + assert.fail('Unexpected resolving'); + }, err => { + assert.ok(atomicChainCompleted === false); + assert.ok(err instanceof CanceledError); + }) + + setTimeout(() => { + chain.cancel(); + }, 50); + + return chain; + } + }, + + 'atomic("await")': { + 'must protect the promise chain from being canceled from the outside with waiting for it to complete': async () => { + let atomicChainCompleted = false; + const chain = CPromise.delay(200, 1) + .then(v => CPromise.delay(200, v + 2)) + .then(result => { + atomicChainCompleted = true; + assert.strictEqual(result, 3); + }, err => { + assert.fail(`Unexpected rejecting: ${err}`); + }) + .atomic('await') + .then(v => CPromise.delay(1000)) + .then(v => { + assert.fail('Unexpected resolving'); + }, err => { + assert.ok(atomicChainCompleted === true); + assert.ok(err instanceof CanceledError); + assert.strictEqual(err.message, 'test'); + }) + + setTimeout(() => { + chain.cancel('test'); + }, 50); + + return chain; + } } + } };