Skip to content

Commit

Permalink
Introduced AsyncGeneratorScope class;
Browse files Browse the repository at this point in the history
Updated README.md;
  • Loading branch information
DigitalBrainJS committed Sep 17, 2020
1 parent de3296e commit 2d1f427
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 59 deletions.
85 changes: 68 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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) ⇒ <code>CPromiseScope</code>
* [.totalWeight](#module_CPromise..AsyncGeneratorScope+totalWeight) ⇒ <code>Number</code>
* [.captureProgress(totalWeight, throttle)](#module_CPromise..AsyncGeneratorScope+captureProgress)
* [~CPromiseScope](#module_CPromise..CPromiseScope) ⇐ <code>TinyEventEmitter</code>
* [new CPromiseScope(resolve, reject, options)](#new_module_CPromise..CPromiseScope_new)
* _instance_
Expand Down Expand Up @@ -288,6 +294,51 @@ Cancellable Promise with extra features
* [~CPromiseExecutorFn](#module_CPromise..CPromiseExecutorFn) : <code>function</code>
* [~CPromiseOptions](#module_CPromise..CPromiseOptions) : <code>Object</code> \| <code>String</code> \| <code>Number</code>

<a name="module_CPromise..AsyncGeneratorScope"></a>

### CPromise~AsyncGeneratorScope
Scope for generator resolvers

**Kind**: inner class of [<code>CPromise</code>](#module_CPromise)

* [~AsyncGeneratorScope](#module_CPromise..AsyncGeneratorScope)
* [new AsyncGeneratorScope(scope)](#new_module_CPromise..AsyncGeneratorScope_new)
* [.scope](#module_CPromise..AsyncGeneratorScope+scope) ⇒ <code>CPromiseScope</code>
* [.totalWeight](#module_CPromise..AsyncGeneratorScope+totalWeight) ⇒ <code>Number</code>
* [.captureProgress(totalWeight, throttle)](#module_CPromise..AsyncGeneratorScope+captureProgress)

<a name="new_module_CPromise..AsyncGeneratorScope_new"></a>

#### new AsyncGeneratorScope(scope)
Creates a new AsyncGeneratorScope instance


| Param | Type |
| --- | --- |
| scope | <code>CPromiseScope</code> |

<a name="module_CPromise..AsyncGeneratorScope+scope"></a>

#### asyncGeneratorScope.scope ⇒ <code>CPromiseScope</code>
Promise scope related to the generator

**Kind**: instance property of [<code>AsyncGeneratorScope</code>](#module_CPromise..AsyncGeneratorScope)
<a name="module_CPromise..AsyncGeneratorScope+totalWeight"></a>

#### asyncGeneratorScope.totalWeight ⇒ <code>Number</code>
total weight of the inner chains produced by the generator

**Kind**: instance property of [<code>AsyncGeneratorScope</code>](#module_CPromise..AsyncGeneratorScope)
<a name="module_CPromise..AsyncGeneratorScope+captureProgress"></a>

#### asyncGeneratorScope.captureProgress(totalWeight, throttle)
**Kind**: instance method of [<code>AsyncGeneratorScope</code>](#module_CPromise..AsyncGeneratorScope)

| Param | Type | Description |
| --- | --- | --- |
| totalWeight | <code>Number</code> | total weight if generator inner chains |
| throttle | <code>Number</code> | |

<a name="module_CPromise..CPromiseScope"></a>

### CPromise~CPromiseScope ⇐ <code>TinyEventEmitter</code>
Expand Down
35 changes: 18 additions & 17 deletions jsdoc2md/README.hbs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down
81 changes: 60 additions & 21 deletions lib/c-promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand All @@ -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;
}
Expand Down Expand Up @@ -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();

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -73,6 +73,7 @@
"promise",
"cancelable",
"cancellable",
"timeout",
"progress",
"cancel",
"abortable",
Expand Down
6 changes: 3 additions & 3 deletions test/tests/CPromise.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 2d1f427

Please sign in to comment.