Skip to content

Commit

Permalink
Fixed all and race methods to support iterable input;
Browse files Browse the repository at this point in the history
Updated README.hbs.md;
  • Loading branch information
DigitalBrainJS committed Sep 14, 2020
1 parent 3d19942 commit 6829b41
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 190 deletions.
36 changes: 25 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Of course, if don't need cancellation, capture progress etc. you may use plain a
## Usage example
Handling cancellation with `onCancel` listeners (see the [live demo](https://runkit.com/digitalbrainjs/runkit-npm-c-promise2)):
````javascript
import CPromise from "c-promise";
import CPromise from "c-promise2";

const timestamp= Date.now();

Expand All @@ -140,8 +140,7 @@ function log(message, ...values){

const delay= (ms, value)=>{
return new CPromise((resolve, reject, {onCancel}) => {
const timer = setTimeout(resolve, ms, value);

const timer = setTimeout(resolve, ms, value);
onCancel(() => {
log(`clearTimeout`);
clearTimeout(timer);
Expand Down Expand Up @@ -234,7 +233,7 @@ Cancellable Promise with extra features
* [.signal](#module_CPromise..CPromiseScope+signal) : <code>AbortSignal</code>
* [.isPending](#module_CPromise..CPromiseScope+isPending) ⇒ <code>Boolean</code>
* [.isCanceled](#module_CPromise..CPromiseScope+isCanceled) ⇒ <code>Boolean</code>
* [.onCancel(listener)](#module_CPromise..CPromiseScope+onCancel)
* [.onCancel(listener)](#module_CPromise..CPromiseScope+onCancel) ⇒ <code>CPromiseScope</code>
* [.progress([value], [data])](#module_CPromise..CPromiseScope+progress)
* [.debounceProgress(minTick)](#module_CPromise..CPromiseScope+debounceProgress) ⇒ <code>CPromiseScope</code>
* [.propagate(type, data)](#module_CPromise..CPromiseScope+propagate) ⇒ <code>CPromiseScope</code>
Expand Down Expand Up @@ -266,10 +265,11 @@ Cancellable Promise with extra features
* [.delay(ms, value)](#module_CPromise..CPromise.delay) ⇒ <code>CPromise</code>
* [.all(thenables)](#module_CPromise..CPromise.all) ⇒ <code>CPromise</code>
* [.race(thenables)](#module_CPromise..CPromise.race) ⇒ <code>CPromise</code>
* [.from(thing)](#module_CPromise..CPromise.from) ⇒ <code>CPromise</code>
* [.from(thing, [args])](#module_CPromise..CPromise.from) ⇒ <code>CPromise</code> \| <code>null</code>
* [~PromiseScopeOptions](#module_CPromise..PromiseScopeOptions) : <code>Object</code>
* [~onFulfilled](#module_CPromise..onFulfilled) : <code>function</code>
* [~onRejected](#module_CPromise..onRejected) : <code>function</code>
* [~OnCancelListener](#module_CPromise..OnCancelListener) : <code>function</code>
* [~CPromiseExecutorFn](#module_CPromise..CPromiseExecutorFn) : <code>function</code>
* [~CPromiseExecutorFn](#module_CPromise..CPromiseExecutorFn) : <code>function</code>
* [~CPromiseOptions](#module_CPromise..CPromiseOptions) : <code>Object</code> \| <code>String</code> \| <code>Number</code>
Expand All @@ -288,7 +288,7 @@ Scope for CPromises instances
* [.signal](#module_CPromise..CPromiseScope+signal) : <code>AbortSignal</code>
* [.isPending](#module_CPromise..CPromiseScope+isPending) ⇒ <code>Boolean</code>
* [.isCanceled](#module_CPromise..CPromiseScope+isCanceled) ⇒ <code>Boolean</code>
* [.onCancel(listener)](#module_CPromise..CPromiseScope+onCancel)
* [.onCancel(listener)](#module_CPromise..CPromiseScope+onCancel) ⇒ <code>CPromiseScope</code>
* [.progress([value], [data])](#module_CPromise..CPromiseScope+progress)
* [.debounceProgress(minTick)](#module_CPromise..CPromiseScope+debounceProgress) ⇒ <code>CPromiseScope</code>
* [.propagate(type, data)](#module_CPromise..CPromiseScope+propagate) ⇒ <code>CPromiseScope</code>
Expand Down Expand Up @@ -336,14 +336,14 @@ indicates if the promise is pending
**Kind**: instance property of [<code>CPromiseScope</code>](#module_CPromise..CPromiseScope)
<a name="module_CPromise..CPromiseScope+onCancel"></a>

#### cPromiseScope.onCancel(listener)
#### cPromiseScope.onCancel(listener) ⇒ <code>CPromiseScope</code>
registers the listener for cancel event

**Kind**: instance method of [<code>CPromiseScope</code>](#module_CPromise..CPromiseScope)

| Param | Type |
| --- | --- |
| listener | <code>function</code> |
| listener | <code>OnCancelListener</code> |

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

Expand Down Expand Up @@ -511,7 +511,7 @@ CPromise class
* [.delay(ms, value)](#module_CPromise..CPromise.delay) ⇒ <code>CPromise</code>
* [.all(thenables)](#module_CPromise..CPromise.all) ⇒ <code>CPromise</code>
* [.race(thenables)](#module_CPromise..CPromise.race) ⇒ <code>CPromise</code>
* [.from(thing)](#module_CPromise..CPromise.from) ⇒ <code>CPromise</code>
* [.from(thing, [args])](#module_CPromise..CPromise.from) ⇒ <code>CPromise</code> \| <code>null</code>

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

Expand Down Expand Up @@ -657,15 +657,20 @@ returns a promise that fulfills or rejects as soon as one of the promises in an

<a name="module_CPromise..CPromise.from"></a>

#### CPromise.from(thing) ⇒ <code>CPromise</code>
Converts thing to CPromise. If thing if a thenable with cancel method it will be called on cancel event
#### CPromise.from(thing, [args]) ⇒ <code>CPromise</code> \| <code>null</code>
Converts thing to CPromise.

**Kind**: static method of [<code>CPromise</code>](#module_CPromise..CPromise)

| Param | Type |
| --- | --- |
| thing | <code>\*</code> |
| [args] | <code>Array</code> |

**Example**
```js
- CPromise instance returns as is- Thenable wraps into a new CPromise instance, if thenable has the `cancel` method it will be used for canceling- Generator function will be resolved to CPromise- Array will be resoled via CPromise.all, arrays with one element ([[1000]]) wll be resolved via CPromise.race- Number will be converted to CPromise.delay
```
<a name="module_CPromise..PromiseScopeOptions"></a>

### CPromise~PromiseScopeOptions : <code>Object</code>
Expand Down Expand Up @@ -700,6 +705,15 @@ Converts thing to CPromise. If thing if a thenable with cancel method it will be
| err | |
| scope | <code>CPromiseScope</code> |

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

### CPromise~OnCancelListener : <code>function</code>
**Kind**: inner typedef of [<code>CPromise</code>](#module_CPromise)

| Param | Type |
| --- | --- |
| reason | <code>CanceledError</code> |

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

### CPromise~CPromiseExecutorFn : <code>function</code>
Expand Down
5 changes: 2 additions & 3 deletions jsdoc2md/README.hbs.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Of course, if don't need cancellation, capture progress etc. you may use plain a
## Usage example
Handling cancellation with `onCancel` listeners (see the [live demo](https://runkit.com/digitalbrainjs/runkit-npm-c-promise2)):
````javascript
import CPromise from "c-promise";
import CPromise from "c-promise2";

const timestamp= Date.now();

Expand All @@ -140,8 +140,7 @@ function log(message, ...values){

const delay= (ms, value)=>{
return new CPromise((resolve, reject, {onCancel}) => {
const timer = setTimeout(resolve, ms, value);

const timer = setTimeout(resolve, ms, value);
onCancel(() => {
log(`clearTimeout`);
clearTimeout(timer);
Expand Down
146 changes: 84 additions & 62 deletions lib/c-promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ function isGenerator(thing){
return thing && typeof thing==='object' && typeof thing.next==='function' && typeof thing.throw==='function';
}

const iterableToArray = Array.from ? Array.from : (iterable) => Array.prototype.slice.call(iterable);

function resolveGenerator(generatorFn, args){
return new CPromise((resolve, reject, scope)=>{
return new this((resolve, reject, scope)=>{
let totalWeight= 1;

const context= {
Expand Down Expand Up @@ -114,7 +116,7 @@ function resolveGenerator(generatorFn, args){
}

scope.on('cancelhook', (err, scope)=>{
if(promise instanceof CPromise){
if(promise instanceof this){
return promise.cancel(err)
}

Expand All @@ -130,13 +132,13 @@ function resolveGenerator(generatorFn, args){
totalWeight = scope.weight();
}

promise= toCPromise.call(this, r.value);
promise= this.from(r.value);


sum+= weight;
weight= promise.isChain? 1 : promise.weight();

if(promise instanceof CPromise){
if(promise instanceof this){
_setImmediate(()=>{
isCaptured && promise.progress((value, scope, data)=>{
setProgress(value, scope, data);
Expand All @@ -156,52 +158,6 @@ function resolveGenerator(generatorFn, args){
})
}

function arrayToCPromise(arr) {
if (arr.length === 1) {
const first = arr[0];
if (Array.isArray(first)) {
return CPromise.race(first.map(toCPromise, this));
}
}
return CPromise.all(arr.map(toCPromise, this))
}

function toCPromise(thing, args){
if(thing instanceof CPromise){
return thing;
}

if(isThenable(thing)){
return new this((resolve, reject, {onCancel}) => {
if (typeof thing.cancel === 'function') {
onCancel(reason => {
try {
thing.cancel(reason);
} catch (err) {
reject(err);
}
});
}
return thing.then(resolve, reject);
});
}

if(isGeneratorFunction(thing)){
return resolveGenerator.call(this, thing, args)
}

if(Array.isArray(thing)) {
return arrayToCPromise.call(this, thing);
}

switch(typeof thing){
case 'number':
return CPromise.delay(thing);
}

return null;
}

/**
* @typedef PromiseScopeOptions {Object}
* @property {String} label - the label for the promise
Expand Down Expand Up @@ -293,16 +249,23 @@ class CPromiseScope extends TinyEventEmitter {
});
}

/**
* @typedef {Function} OnCancelListener
* @param {CanceledError} reason
*/

/**
* registers the listener for cancel event
* @param {Function} listener
* @param {OnCancelListener} listener
* @return {CPromiseScope}
*/

onCancel(listener) {
if (!this[_isPending]) {
throw Error('Unable to subscribe to close event since promise has been already settled');
}
this.on('cancel', listener);
return this;
}

/**
Expand Down Expand Up @@ -901,7 +864,6 @@ class CPromise extends Promise {
}
const timer= setTimeout(() => resolve(value), ms);
scope.onCancel(()=> clearTimeout(timer));
scope[_shadow].label= `delay(${ms})`;
})
}

Expand All @@ -914,29 +876,37 @@ class CPromise extends Promise {

static all(thenables) {
return new this((resolve, reject, scope)=>{
const cancel= (reason)=>{
for (let i = 0; i < thenables.length; i++) {
thenables= iterableToArray(thenables);

const {length}= thenables;

for (let i = 0; i < length; i++) {
thenables[i]= this.from(thenables[i]) || this.resolve(thenables[i]);
}

const cancel = (reason) => {
for (let i = 0; i < length; i++) {
thenables[i].cancel(reason);
}
};

scope.on('capture', ()=>{
let {length}= thenables;
const progress= new Array(length);
for (let i = 0; i < length; i++) {
const index= i;
thenables[i].progress((value, scope, data)=>{
progress[index]= value;
let sum=0;
for(let i=0; i<length; i++){
progress[i] && (sum+= progress[i]);
for (let i = 0; i < length; i++) {
progress[i] && (sum += progress[i]);
}
scope.progress(sum / length, scope, data);
})
}
})

scope.onCancel(cancel);

super.all(thenables).then(resolve, (err)=>{
reject(err);
cancel();
Expand All @@ -953,14 +923,21 @@ class CPromise extends Promise {

static race(thenables) {
return new this((resolve, reject, scope) => {
thenables= iterableToArray(thenables);

const {length}= thenables;

for (let i = 0; i < length; i++) {
thenables[i]= this.from(thenables[i]) || this.resolve(thenables[i]);
}

const cancel = (reason) => {
for (let i = 0; i < thenables.length; i++) {
for (let i = 0; i < length; i++) {
thenables[i].cancel(reason);
}
};

scope.on('capture', () => {
let {length} = thenables;
let max = 0;
for (let i = 0; i < length; i++) {
thenables[i].progress((value, scope, data) => {
Expand All @@ -987,13 +964,58 @@ class CPromise extends Promise {
}

/**
* Converts thing to CPromise. If thing if a thenable with cancel method it will be called on cancel event
* Converts thing to CPromise.
* @example
* - CPromise instance returns as is
* - Thenable wraps into a new CPromise instance, if thenable has the `cancel` method it will be used for canceling
* - Generator function will be resolved to CPromise
* - Array will be resoled via CPromise.all, arrays with one element ([[1000]]) wll be resolved via CPromise.race
* - Number will be converted to CPromise.delay
* @param {*} thing
* @returns {CPromise}
* @param {Array} [args]
* @returns {CPromise|null}
*/

static from(thing, args) {
return toCPromise.call(this, thing, args);
if (thing instanceof this) {
return thing;
}

if (isThenable(thing)) {
return new this((resolve, reject, {onCancel}) => {
if (typeof thing.cancel === 'function') {
onCancel(reason => {
try {
thing.cancel(reason);
} catch (err) {
reject(err);
}
});
}
return thing.then(resolve, reject);
});
}

if (isGeneratorFunction(thing)) {
return resolveGenerator.call(this, thing, args)
}

if (Array.isArray(thing)) {
if (thing.length === 1) {
const first = thing[0];
if (Array.isArray(first)) {
return this.race(first.map(this.from, this));
}
}
return this.all(thing.map(this.from, this))
}

switch (typeof thing) {
case 'number':
return this.delay(thing);
}

return null;
}
}

Expand Down
Loading

0 comments on commit 6829b41

Please sign in to comment.