Skip to content

Commit

Permalink
Added generator support for the then method;
Browse files Browse the repository at this point in the history
Added CPromise.resolveGenerator method;
Refactored CPromise.from method;
  • Loading branch information
DigitalBrainJS committed Nov 30, 2020
1 parent fb63e36 commit ee41529
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 117 deletions.
38 changes: 34 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
![npm](https://img.shields.io/npm/dm/c-promise2)
![npm bundle size](https://img.shields.io/bundlephobia/minzip/c-promise2)
![David](https://img.shields.io/david/DigitalBrainJS/c-promise)
![Stars](https://badgen.net/github/stars/DigitalBrainJS/c-promise)
[![Stars](https://badgen.net/github/stars/DigitalBrainJS/c-promise)](https://github.com/DigitalBrainJS/c-promise/stargazers)

## Table of contents
- [SYNOPSIS](#synopsis-sparkles)
Expand Down Expand Up @@ -457,6 +457,19 @@ const promise= CPromise.from(function*(){
.progress(value=> console.log(`Progress: ${value}`))
.then(message=> console.log(`Done: ${message}`));
````
`Then` method also supports generators as callback function
````javascript
CPromise.resolve().then(function*(){
const value1= yield CPromise.delay(3000, 3);
// Run promises in parallel using CPromise.all (shortcut syntax)
const [value2, value3]= yield [CPromise.delay(3000, 4), CPromise.delay(3000, 5)]
return value1 + value2 + value3;
}).then(value=>{
console.log(`Done: ${value}`); // Done: 12
}, err=>{
console.log(`Failed: ${err}`);
})
````

## Related projects
- [cp-axios](https://www.npmjs.com/package/cp-axios) - a simple axios wrapper that provides an advanced cancellation api
Expand Down Expand Up @@ -516,7 +529,8 @@ CPromise class
* [.all(iterable, options)](#module_CPromise..CPromise.all) ⇒ <code>CPromise</code>
* [.race(thenables)](#module_CPromise..CPromise.race) ⇒ <code>CPromise</code>
* [.allSettled(iterable, options)](#module_CPromise..CPromise.allSettled) ⇒ <code>CPromise</code>
* [.from(thing, [resolveSignatures])](#module_CPromise..CPromise.from) ⇒ <code>CPromise</code>
* [.from(thing, [options])](#module_CPromise..CPromise.from) ⇒ <code>CPromise</code>
* [.resolveGenerator(generatorFn, [options])](#module_CPromise..CPromise.resolveGenerator) ⇒ <code>CPromise</code>

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

Expand Down Expand Up @@ -929,15 +943,31 @@ returns a promise that resolves after all of the given promises have either fulf

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

#### CPromise.from(thing, [resolveSignatures]) ⇒ <code>CPromise</code>
#### CPromise.from(thing, [options]) ⇒ <code>CPromise</code>
Converts thing to CPromise using the following rules:- CPromise instance returns as is- Objects with special method defined with key `Symbol.for('toCPromise')` will be converted using this method The result will be cached for future calls- 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 (e.g. `[[1000]]`) will be resolved via `CPromise.race`This method returns null if the conversion failed.

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

| Param | Type | Default |
| --- | --- | --- |
| thing | <code>\*</code> | |
| [resolveSignatures] | <code>boolean</code> | <code>true</code> |
| [options] | <code>Object</code> | |
| [options.resolveSignatures] | <code>Boolean</code> | <code>true</code> |
| [options.args] | <code>Array</code> | |

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

#### CPromise.resolveGenerator(generatorFn, [options]) ⇒ <code>CPromise</code>
Resolves the generator to an CPromise instance

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

| Param | Type |
| --- | --- |
| generatorFn | <code>GeneratorFunction</code> |
| [options] | <code>Object</code> |
| [options.args] | <code>Array</code> |
| [options.resolveSignatures] | <code>Boolean</code> |

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

Expand Down
15 changes: 14 additions & 1 deletion jsdoc2md/README.hbs.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
![npm](https://img.shields.io/npm/dm/c-promise2)
![npm bundle size](https://img.shields.io/bundlephobia/minzip/c-promise2)
![David](https://img.shields.io/david/DigitalBrainJS/c-promise)
![Stars](https://badgen.net/github/stars/DigitalBrainJS/c-promise)
[![Stars](https://badgen.net/github/stars/DigitalBrainJS/c-promise)](https://github.com/DigitalBrainJS/c-promise/stargazers)

## Table of contents
- [SYNOPSIS](#synopsis-sparkles)
Expand Down Expand Up @@ -457,6 +457,19 @@ const promise= CPromise.from(function*(){
.progress(value=> console.log(`Progress: ${value}`))
.then(message=> console.log(`Done: ${message}`));
````
`Then` method also supports generators as callback function
````javascript
CPromise.resolve().then(function*(){
const value1= yield CPromise.delay(3000, 3);
// Run promises in parallel using CPromise.all (shortcut syntax)
const [value2, value3]= yield [CPromise.delay(3000, 4), CPromise.delay(3000, 5)]
return value1 + value2 + value3;
}).then(value=>{
console.log(`Done: ${value}`); // Done: 12
}, err=>{
console.log(`Failed: ${err}`);
})
````

## Related projects
- [cp-axios](https://www.npmjs.com/package/cp-axios) - a simple axios wrapper that provides an advanced cancellation api
Expand Down
242 changes: 130 additions & 112 deletions lib/c-promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,92 +38,6 @@ const computeWeightSum = promises => {
return sum;
}

function resolveGenerator(generatorFn) {
return new this((resolve, reject, scope) => {
const generator = generatorFn.call(scope, scope);

if (!isGenerator(generator)) {
return reject(new TypeError('function must return a generator object'));
}

let isCaptured;
let progress = 0;
let sum = 0;
let weight = 0;
let promise;

scope.on('capture', () => {
isCaptured = true;
});

const setProgress = (value, _scope, data) => {
progress = (value * weight + sum) / scope.innerWeight();
if (progress > 1) {
progress = 1;
}
scope.progress(progress, _scope, data);
}

const onFulfilled = (result) => {
try {
next(generator.next(result));
} catch (e) {
return reject(e);
}
}

const onRejected = (err) => {
try {
next(generator.throw(err));
} catch (e) {
reject(e);
}
}

scope.on('signal', (type, data) => {
switch (type) {
case SIGNAL_CANCEL:
if (!promise.cancel(data.err, data.force)) {
onRejected(data.err);
}
return true;
case SIGNAL_PAUSE:
promise.pause();
return true;
case SIGNAL_RESUME:
promise.resume();
return true;
}

return promise.emitSignal(type, data);
});

const next = (r) => {
if (r.done) {
return resolve(r.value);
}

promise = this.from(r.value);

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

setImmediate(() => {
isCaptured && promise.progress((value, scope, data) => {
setProgress(value, scope, data);
});
});

return promise.then((value) => {
setProgress(1, promise);
onFulfilled(value)
}, onRejected);
}

onFulfilled();
})
}

/**
* @typedef {Function} CPromiseExecutorFn
* @this CPromise
Expand Down Expand Up @@ -848,27 +762,6 @@ class CPromise extends Promise {
return emit(this, true);
}

emitSignal2(type, data, handler, selector) {
const shadow = this[_shadow];
if (!shadow.isPending) return false;

if(selector && selector.call(this, data, type, this)){

}

let {parent, innerChain} = shadow;

if (parent && parent.emitSignal(type, data, handler)) {
return true;
}

if (innerChain && innerChain.emitSignal(type, data, handler)) {
return true;
}

return !!(this.emitHook('signal', type, data) || handler && handler.call(this, data, type, this));
}

/**
* Returns a chain that will be resolved after specified timeout
* @param {Number} ms
Expand All @@ -888,7 +781,14 @@ class CPromise extends Promise {

then(onFulfilled, onRejected) {
const promise = super.then(
onFulfilled ? ((value) => onFulfilled.call(promise, value, promise)) : value => {
onFulfilled ? ((value) => {
return isGeneratorFunction(onFulfilled) ?
this.constructor.resolveGenerator(onFulfilled, {
resolveSignatures: true,
args: [value]
}) :
onFulfilled.call(promise, value, promise)
}) : value => {
if (this[_shadow].isCanceled) {
promise[_shadow].isCanceled = true;
}
Expand All @@ -898,7 +798,12 @@ class CPromise extends Promise {
if (err instanceof CanceledError) {
promise[_shadow].isCanceled = true;
}
return onRejected.call(promise, err, promise)
return isGeneratorFunction(onRejected) ?
this.constructor.resolveGenerator(onFulfilled, {
resolveSignatures: true,
args: [err]
})
: onRejected.call(promise, err, promise)
})
);

Expand Down Expand Up @@ -1315,15 +1220,19 @@ class CPromise extends Promise {
*
* This method returns null if the conversion failed.
* @param {*} thing
* @param {boolean} [resolveSignatures= true]
* @param {Object} [options]
* @param {Boolean} [options.resolveSignatures= true]
* @param {Array} [options.args]
* @returns {CPromise}
*/

static from(thing, resolveSignatures = true) {
static from(thing, options) {
if (thing && thing instanceof this) {
return thing;
}

const {resolveSignatures= true, args} = (typeof options === 'boolean' ? {resolveSignatures: options} : options) || {};

if (resolveSignatures) {
const type = typeof thing;

Expand All @@ -1339,7 +1248,7 @@ class CPromise extends Promise {
}
} else if (type === 'function') {
if (isGeneratorFunction(thing)) {
return resolveGenerator.call(this, thing)
return this.resolveGenerator(thing, args)
}
}

Expand All @@ -1349,6 +1258,115 @@ class CPromise extends Promise {
return this.resolve(thing);
}

/**
* Resolves the generator to an CPromise instance
* @param {GeneratorFunction} generatorFn
* @param {Object} [options]
* @param {Array} [options.args]
* @param {Boolean} [options.resolveSignatures]
* @returns {CPromise}
*/

static resolveGenerator(generatorFn, {args, resolveSignatures}= {}) {
return new this((resolve, reject, scope) => {
let generator;

switch (args ? args.length : 0) {
case 0:
generator = generatorFn.call(scope, scope);
break;
case 1:
generator = generatorFn.call(scope, args[0], scope);
break;
case 2:
generator = generatorFn.call(scope, args[0], args[1], scope);
break;
default:
generator = generatorFn.apply(scope, [...args, scope]);
}

if (!isGenerator(generator)) {
return reject(new TypeError('function must a generator'));
}

let isCaptured;
let progress = 0;
let sum = 0;
let weight = 0;
let promise;

scope.on('capture', () => {
isCaptured = true;
});

const setProgress = (value, _scope, data) => {
progress = (value * weight + sum) / scope.innerWeight();
if (progress > 1) {
progress = 1;
}
scope.progress(progress, _scope, data);
}

const onFulfilled = (result) => {
try {
next(generator.next(result));
} catch (e) {
return reject(e);
}
}

const onRejected = (err) => {
try {
next(generator.throw(err));
} catch (e) {
reject(e);
}
}

scope.on('signal', (type, data) => {
switch (type) {
case SIGNAL_CANCEL:
if (!promise.cancel(data.err, data.force)) {
onRejected(data.err);
}
return true;
case SIGNAL_PAUSE:
promise.pause();
return true;
case SIGNAL_RESUME:
promise.resume();
return true;
}

return promise.emitSignal(type, data);
});

const next = (r) => {
if (r.done) {
return resolve(r.value);
}

promise = this.from(r.value, {resolveSignatures});

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

setImmediate(() => {
isCaptured && promise.progress((value, scope, data) => {
setProgress(value, scope, data);
});
});

return promise.then((value) => {
setProgress(1, promise);
onFulfilled(value)
}, onRejected);
}

onFulfilled();
})
}

/**
* adds a new listener
* @param {EventType} type
Expand Down
Loading

0 comments on commit ee41529

Please sign in to comment.