diff --git a/README.md b/README.md index 9b1f2ed..16c21b5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## SYNOPSIS :sparkles: -CPromise is a subclass of the Promise provided by the environment with some extra features +CPromise is built on top of the native Promise provided by the environment with some extra features like cancellation, timeouts and progress capturing. In terms of the library **the cancellation means rejection of the deepest promise in @@ -22,6 +22,23 @@ You may face with a challenge when you need to cancel some long-term asynchronou operation before it will be completed with success or failure, just because the result has lost its relevance to you. +## Features / Advantages +- there are no any dependencies (except [native] Promise), built-in `AbortController` +- browser support +- supports two ways to make your promise internal code cancellable: + - `onCancel` callbacks (clear timers, abort requests) + - `signal` provided by the [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) (to wrap API like +[fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) method) +- :fire: supports cancellation of the whole chain - rejects the deepest pending promise in the chain +- :fire: supports generator to CPromise resolving (something similar like [co](https://www.npmjs.com/package/co) library does); +- :fire: progress capturing with result scaling to handle progress of the whole chain (including nested promise chains), useful for long-term operations +- ability to set the `weight` for each promise in the chain to manage the impact on chain progress +- ability to attach meta info on each setting of the progress +- the `delay` method to return promise that will be resolved with the value after timeout +- static methods `all`, `race` support cancellation and will cancel all other pending + promises after the result promise settled +- the `catch` method supports error class filtering + ## Live Example This is how an abortable fetch ([live example](https://jsfiddle.net/DigitalBrain/c6njyrt9/10/)) with a timeout might look like @@ -72,22 +89,6 @@ callbacks attached by `onCancel(cb)` method and/or propagate with signal from `A These api can be used simultaneously. The `cancel([reason])` method is synchronous and can be called any time. If cancellation failed (the chain has been already fulfilled) it will return `false`. -## Features / Advantages -- there are no any dependencies (except [native] Promise) -- browser support -- :fire: supports cancellation of the whole chain - rejects the deepest pending promise in the chain -- supports onCancel event handler to abort some internal work (clear timers, close requests etc.) -- supports built-in signal interface for API that supports it (like fetch method) -- :fire: supports generator to CPromise resolving (something similar like [co](https://www.npmjs.com/package/co) library does); -- proper handling of `CanceledError` errors manually thrown inside the chain -- :fire: progress capturing with result scaling to handle progress of the whole chain (including nested promise chains), useful for long-term operations -- ability to set the `weight` for each promise in the chain -- ability to attach meta info on each setting of the progress -- the `delay` method to return promise that will be resolved with the value after timeout -- static methods `all`, `race` support cancellation and will cancel all other pending - promises after the result promise settled -- the `catch` method supports error class filtering - ## Installation :hammer: - Install for node.js using npm/yarn: @@ -241,6 +242,11 @@ Cancellable Promise with extra features * [CPromise](#module_CPromise) + * [~AsyncGeneratorScope](#module_CPromise..AsyncGeneratorScope) + * [new AsyncGeneratorScope(scope)](#new_module_CPromise..AsyncGeneratorScope_new) + * [.scope](#module_CPromise..AsyncGeneratorScope+scope) ⇒ CPromiseScope + * [.totalWeight](#module_CPromise..AsyncGeneratorScope+totalWeight) ⇒ Number + * [.captureProgress(totalWeight, throttle)](#module_CPromise..AsyncGeneratorScope+captureProgress) * [~CPromiseScope](#module_CPromise..CPromiseScope) ⇐ TinyEventEmitter * [new CPromiseScope(resolve, reject, options)](#new_module_CPromise..CPromiseScope_new) * _instance_ @@ -288,6 +294,51 @@ Cancellable Promise with extra features * [~CPromiseExecutorFn](#module_CPromise..CPromiseExecutorFn) : function * [~CPromiseOptions](#module_CPromise..CPromiseOptions) : Object \| String \| Number + + +### CPromise~AsyncGeneratorScope +Scope for generator resolvers + +**Kind**: inner class of [CPromise](#module_CPromise) + +* [~AsyncGeneratorScope](#module_CPromise..AsyncGeneratorScope) + * [new AsyncGeneratorScope(scope)](#new_module_CPromise..AsyncGeneratorScope_new) + * [.scope](#module_CPromise..AsyncGeneratorScope+scope) ⇒ CPromiseScope + * [.totalWeight](#module_CPromise..AsyncGeneratorScope+totalWeight) ⇒ Number + * [.captureProgress(totalWeight, throttle)](#module_CPromise..AsyncGeneratorScope+captureProgress) + + + +#### new AsyncGeneratorScope(scope) +Creates a new AsyncGeneratorScope instance + + +| Param | Type | +| --- | --- | +| scope | CPromiseScope | + + + +#### asyncGeneratorScope.scope ⇒ CPromiseScope +Promise scope related to the generator + +**Kind**: instance property of [AsyncGeneratorScope](#module_CPromise..AsyncGeneratorScope) + + +#### asyncGeneratorScope.totalWeight ⇒ Number +total weight of the inner chains produced by the generator + +**Kind**: instance property of [AsyncGeneratorScope](#module_CPromise..AsyncGeneratorScope) + + +#### asyncGeneratorScope.captureProgress(totalWeight, throttle) +**Kind**: instance method of [AsyncGeneratorScope](#module_CPromise..AsyncGeneratorScope) + +| Param | Type | Description | +| --- | --- | --- | +| totalWeight | Number | total weight if generator inner chains | +| throttle | Number | | + ### CPromise~CPromiseScope ⇐ TinyEventEmitter diff --git a/jsdoc2md/README.hbs.md b/jsdoc2md/README.hbs.md index 7209643..ef0ac4b 100644 --- a/jsdoc2md/README.hbs.md +++ b/jsdoc2md/README.hbs.md @@ -6,7 +6,7 @@ ## SYNOPSIS :sparkles: -CPromise is a subclass of the Promise provided by the environment with some extra features +CPromise is built on top of the native Promise provided by the environment with some extra features like cancellation, timeouts and progress capturing. In terms of the library **the cancellation means rejection of the deepest promise in @@ -22,6 +22,23 @@ You may face with a challenge when you need to cancel some long-term asynchronou operation before it will be completed with success or failure, just because the result has lost its relevance to you. +## Features / Advantages +- there are no any dependencies (except [native] Promise), built-in `AbortController` +- browser support +- supports two ways to make your promise internal code cancellable: + - `onCancel` callbacks (clear timers, abort requests) + - `signal` provided by the [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) (to wrap API like +[fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) method) +- :fire: supports cancellation of the whole chain - rejects the deepest pending promise in the chain +- :fire: supports generator to CPromise resolving (something similar like [co](https://www.npmjs.com/package/co) library does); +- :fire: progress capturing with result scaling to handle progress of the whole chain (including nested promise chains), useful for long-term operations +- ability to set the `weight` for each promise in the chain to manage the impact on chain progress +- ability to attach meta info on each setting of the progress +- the `delay` method to return promise that will be resolved with the value after timeout +- static methods `all`, `race` support cancellation and will cancel all other pending + promises after the result promise settled +- the `catch` method supports error class filtering + ## Live Example This is how an abortable fetch ([live example](https://jsfiddle.net/DigitalBrain/c6njyrt9/10/)) with a timeout might look like @@ -72,22 +89,6 @@ callbacks attached by `onCancel(cb)` method and/or propagate with signal from `A These api can be used simultaneously. The `cancel([reason])` method is synchronous and can be called any time. If cancellation failed (the chain has been already fulfilled) it will return `false`. -## Features / Advantages -- there are no any dependencies (except [native] Promise) -- browser support -- :fire: supports cancellation of the whole chain - rejects the deepest pending promise in the chain -- supports onCancel event handler to abort some internal work (clear timers, close requests etc.) -- supports built-in signal interface for API that supports it (like fetch method) -- :fire: supports generator to CPromise resolving (something similar like [co](https://www.npmjs.com/package/co) library does); -- proper handling of `CanceledError` errors manually thrown inside the chain -- :fire: progress capturing with result scaling to handle progress of the whole chain (including nested promise chains), useful for long-term operations -- ability to set the `weight` for each promise in the chain -- ability to attach meta info on each setting of the progress -- the `delay` method to return promise that will be resolved with the value after timeout -- static methods `all`, `race` support cancellation and will cancel all other pending - promises after the result promise settled -- the `catch` method supports error class filtering - ## Installation :hammer: - Install for node.js using npm/yarn: diff --git a/lib/c-promise.js b/lib/c-promise.js index f64c340..3aaa398 100644 --- a/lib/c-promise.js +++ b/lib/c-promise.js @@ -63,28 +63,72 @@ function isGenerator(thing) { const iterableToArray = Array.from ? Array.from : (iterable) => Array.prototype.slice.call(iterable); -function resolveGenerator(generatorFn, args) { - return new this((resolve, reject, scope) => { - let totalWeight = 1; +/** + * Scope for generator resolvers + */ - const context = { - scope, - captureProgress: (weight) => { - if (typeof weight !== 'number') { - throw TypeError('weight must be a number'); - } - if (weight < 1) { - throw Error('weight must be grater than 0'); - } - totalWeight = weight; - } +class AsyncGeneratorScope { + /** + * Creates a new AsyncGeneratorScope instance + * @param {CPromiseScope} scope + */ + constructor(scope) { + this[_shadow]= { + totalWeight: 1, + scope + } + } + + /** + * Promise scope related to the generator + * @return {CPromiseScope} + */ + + get scope(){ + return this[_shadow].scope; + } + + /** + * total weight of the inner chains produced by the generator + * @return {Number} + */ + + get totalWeight(){ + return this[_shadow].totalWeight; + } + + set totalWeight(value){ + if (typeof value !== 'number') { + throw TypeError('weight must be a number'); } + if (value < 1) { + throw Error('weight must be grater than 0'); + } + + this[_shadow].totalWeight = value; + } + + /** + * + * @param {Number} totalWeight total weight if generator inner chains + * @param {Number} throttle + */ + captureProgress(totalWeight, throttle) { + this.totalWeight= totalWeight; + throttle && this[_scope].throttleProgress(totalWeight); + } +} + +function resolveGenerator(generatorFn, args) { + return new this((resolve, reject, scope) => { if (args !== undefined && !Array.isArray(args)) { throw TypeError('args must be an array'); } - const generator = generatorFn.apply(context, args); + const generatorScope= new AsyncGeneratorScope(scope); + + const generator = generatorFn.apply(generatorScope, args); if (!isGenerator(generator)) { return reject(new TypeError('function must return a generator object')); @@ -101,7 +145,7 @@ function resolveGenerator(generatorFn, args) { }); const setProgress = (value, _scope, data) => { - progress = (value * weight + sum) / (totalWeight ? totalWeight : 1); + progress = (value * weight + sum) / generatorScope.totalWeight; if (progress > 1) { progress = 1; } @@ -137,13 +181,8 @@ function resolveGenerator(generatorFn, args) { return resolve(r.value); } - if (!totalWeight) { - totalWeight = scope.weight(); - } - promise = this.from(r.value); - sum += weight; weight = promise.isChain ? 1 : promise.weight(); diff --git a/package.json b/package.json index 265de17..2922134 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "c-promise2", "version": "0.3.1", - "description": "Cancelable promise chain with progress capturing and other extra features", + "description": "Promise with cancellation, progress capturing and other extra features.", "author": { "name": "Dmitriy Mozgovoy", "email": "robotshara@gmail.com", @@ -73,6 +73,7 @@ "promise", "cancelable", "cancellable", + "timeout", "progress", "cancel", "abortable", diff --git a/test/tests/CPromise.js b/test/tests/CPromise.js index c014c5e..8f15f93 100644 --- a/test/tests/CPromise.js +++ b/test/tests/CPromise.js @@ -285,8 +285,8 @@ module.exports = { const timestamp = Date.now(); const time = () => Date.now() - timestamp; return CPromise.all([ - CPromise.delay(50, v1), - CPromise.delay(100, v2) + CPromise.delay(55, v1), + CPromise.delay(105, v2) ]).then((values)=>{ assert.ok(time() >= 100); assert.deepStrictEqual(values, [v1, v2]); @@ -316,7 +316,7 @@ module.exports = { const timestamp = Date.now(); const time = () => Date.now() - timestamp; return CPromise.race([ - CPromise.delay(50, v1), + CPromise.delay(55, v1), CPromise.delay(100, v2) ]).then((value)=>{ assert.ok(time() >= 50 && time() < 100);