Skip to content

Commit

Permalink
Added promisify method;
Browse files Browse the repository at this point in the history
  • Loading branch information
DigitalBrainJS committed Dec 11, 2020
1 parent c49ed41 commit b07585d
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 2 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,7 @@ CPromise class
* [.race(thenables)](#module_CPromise..CPromise.race) ⇒ <code>CPromise</code>
* [.allSettled(iterable, options)](#module_CPromise..CPromise.allSettled) ⇒ <code>CPromise</code>
* [.from(thing, [options])](#module_CPromise..CPromise.from) ⇒ <code>CPromise</code>
* [.promisify(originalFn, [options])](#module_CPromise..CPromise.promisify) ⇒ <code>function</code>
* [.resolveGenerator(generatorFn, [options])](#module_CPromise..CPromise.resolveGenerator) ⇒ <code>CPromise</code>

<a name="new_module_CPromise..CPromise_new"></a>
Expand Down Expand Up @@ -1329,6 +1330,18 @@ Converts thing to CPromise using the following rules: - CPromise instance return
| [options.resolveSignatures] | <code>Boolean</code> | <code>true</code> |
| [options.args] | <code>Array</code> | |

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

#### CPromise.promisify(originalFn, [options]) ⇒ <code>function</code>
Converts callback styled function|GeneratorFn|AsyncFn to CPromise async function

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

| Param | Type |
| --- | --- |
| originalFn | <code>function</code> \| <code>GeneratorFunction</code> \| <code>AsyncFunction</code> |
| [options] | <code>PromisifyOptions</code> \| <code>function</code> \| <code>Boolean</code> |

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

#### CPromise.resolveGenerator(generatorFn, [options]) ⇒ <code>CPromise</code>
Expand Down Expand Up @@ -1447,6 +1460,27 @@ If value is a number it will be considered as the value for timeout option If va
| ignoreResults | <code>boolean</code> | do not collect results |
| signatures | <code>boolean</code> | use advanced signatures for vales resolving |

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

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

| Param | Type |
| --- | --- |
| result | <code>\*</code> |
| scope | <code>CPromise</code> |

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

### CPromise~PromisifyOptions : <code>Object</code>
**Kind**: inner typedef of [<code>CPromise</code>](#module_CPromise)
**Properties**

| Name | Type | Description |
| --- | --- | --- |
| multiArgs | <code>Boolean</code> | aggregate all passed arguments to an array |
| finalize | <code>PromisifyFinalizeFn</code> | aggregate all passed arguments to an array |


## License

Expand Down
71 changes: 70 additions & 1 deletion lib/c-promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const {
isGenerator,
toGenerator,
toArray,
buildDecorator
buildDecorator,
getFnType
} = require('./utils');

const {now} = Date;
Expand Down Expand Up @@ -1309,6 +1310,74 @@ class CPromise extends Promise {
return this.resolve(thing);
}

/**
* @typedef {Function} PromisifyFinalizeFn
* @param {*} result
* @param {CPromise} scope
*/

/**
* @typedef {Object} PromisifyOptions
* @property {Boolean} multiArgs aggregate all passed arguments to an array
* @property {PromisifyFinalizeFn} finalize aggregate all passed arguments to an array
*/

/**
* Converts callback styled function|GeneratorFn|AsyncFn to CPromise async function
* @param {Function|GeneratorFunction|AsyncFunction} originalFn
* @param {PromisifyOptions|Function|Boolean} [options]
* @returns {function(...[*]): CPromise}
*/

static promisify(originalFn, options) {
const type = typeof options;

if (type === 'boolean') {
options = {multiArgs: !!options}
} else if (type === 'function') {
options = {finalize: options};
} else {
options !== undefined && validateOptions(options, {
multiArgs: validators.boolean,
finalize: validators.plainFunction,
fnType: validators.string
});
}

const {multiArgs, finalize} = options || {};
const context = this;

switch (getFnType(originalFn)) {
case "plain":
return (...args) => {
return new this((resolve, reject, scope) => {
const result = originalFn.apply(this, [...args, (err, ...data) => {
if (err) {
return reject(err);
}

return multiArgs || data.length > 1 ? resolve(data) : resolve(data[0]);
}]);

finalize && finalize(result, scope);
})
}
case "generator":
return function (...args) {
return context.resolveGenerator(originalFn, {
context: this,
args
});
}
case "async":
return function () {
return context.from(originalFn.apply(this, arguments), {resolveSignatures: false});
}
}

throw TypeError('promisify requires a Function|GeneratorFunction|AsyncFunction as the first argument');
}

/**
* Resolves the generator to an CPromise instance
* @param {GeneratorFunction} generatorFn
Expand Down
7 changes: 7 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ function isGeneratorFunction(thing) {
return typeof thing === 'function' && thing.constructor && thing.constructor.name === 'GeneratorFunction';
}

const getFnType = (thing) => typeof thing === 'function' && ({
GeneratorFunction: 'generator',
AsyncFunction: 'async',
Function: 'plain'
})[thing.constructor.name];

function isGenerator(thing) {
return thing && typeof thing === 'object' && typeof thing.next === 'function';
}
Expand Down Expand Up @@ -120,5 +126,6 @@ module.exports={
EmptyObject,
toGenerator,
toArray,
getFnType,
buildDecorator
};
7 changes: 6 additions & 1 deletion lib/validator.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
const numberFinitePositive = (thing) => typeof thing === 'number' && Number.isFinite(thing) && thing >= 0
|| 'a positive number';

const plainFunction = (thing)=> typeof thing==='function' && thing.constructor===Function || 'a plain function';

const validators = {
numberFinitePositive
numberFinitePositive,
plainFunction
};

['object', 'boolean', 'number', 'function', 'string', 'symbol'].forEach((type, i) => {
Expand Down Expand Up @@ -30,6 +33,8 @@ function validateOptions(options, schema, allowUnknown = false) {
throw Error(`Unknown option '${option}'`);
}
}

return options;
}

function validateArguments(args, validators, {context = "function"} = {}) {
Expand Down
98 changes: 98 additions & 0 deletions test/tests/CPromise.js
Original file line number Diff line number Diff line change
Expand Up @@ -705,4 +705,102 @@ module.exports = {
assert.ok(invoked2);
}
},

'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(typeof fn === 'function');

const promise = fn(123);

assert.ok(promise instanceof CPromise);

return promise.then(value => {
assert.strictEqual(value, 456);
})
},

'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(typeof fn === 'function');

const promise = fn(123);

assert.ok(promise instanceof CPromise);

return promise.then(value => {
assert.deepStrictEqual(value, [456, 789]);
})
},

'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(typeof fn === 'function');

const promise = fn(123);

assert.ok(promise instanceof CPromise);

return promise.then(value => {
assert.deepStrictEqual(value, [456]);
})
},

'should handle rejection': async function () {
const fn = CPromise.promisify(function (arg0, cb) {
assert.strictEqual(arg0, 123);
setTimeout(cb, 100, new Error('test'));
});

const promise = fn(123);

return promise.then(value => {
assert.fail(`doesn't throw`);
}).catch(err => {
assert.ok(err.message, 'test');
})
}
},

'should support async function decoration': async function () {
const fn = CPromise.promisify(async function (arg0) {
return delay(100, 123);
});

const promise = fn(123);

assert.ok(promise instanceof CPromise);

return promise.then(value => {
assert.deepStrictEqual(value, 123);
})
},

'should support generator function decoration': async function () {
const fn = CPromise.promisify(function* (arg0) {
return yield delay(100, 123);
});

const promise = fn(123);

assert.ok(promise instanceof CPromise);

return promise.then(value => {
assert.deepStrictEqual(value, 123);
})
}
}
};

0 comments on commit b07585d

Please sign in to comment.