Skip to content

Commit

Permalink
comments, addAsync now can handle classes
Browse files Browse the repository at this point in the history
  • Loading branch information
taburetkin committed Nov 20, 2019
1 parent c700d7c commit b537069
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 8 deletions.
26 changes: 25 additions & 1 deletion README.md
Expand Up @@ -5,7 +5,7 @@ Small set of utils for working with promises.
![version](https://img.shields.io/github/package-json/v/taburetkin/asyncresult.svg)
[![Coverage Status](https://coveralls.io/repos/github/taburetkin/asyncresult/badge.svg?branch=master)](https://coveralls.io/github/taburetkin/asyncresult?branch=master)
![Build status](https://secure.travis-ci.org/taburetkin/asyncresult.svg?branch=master)
![TypeScript](https://img.shields.io/badge/ts--definitions-yes-green.svg)


## examples:

Expand Down Expand Up @@ -235,6 +235,30 @@ const Something = {
Something.fooAsync = wrapMethod(Something.foo, { context: Something })
```

Also, its possible to extend prototype of some class
You can do this in two ways
1. patch prototype
```js
addAsync(SomeClass.prototype, [...], { context: null });
```
Note, that there should be null context specified in options.

2. pass class itself as argument
```js
addAsync(SomeClass, [...]);
```
actually its just a suggar for case 1.

**Warning**: Note, that both modifies prototype so you should know what are you doing.

In case you want to add class static methods you have to use option `static`

```javascript
// assume that SomeClass.staticMethod is a function.
addAsync(SomeClass, 'staticMethod', { static: true });
await SomeClass.staticMethodAsync();
```

### arguments

**context** - object, required. Context for methods lookup and for adding asynced methods.
Expand Down
77 changes: 72 additions & 5 deletions asyncResult.js
@@ -1,29 +1,67 @@

/**
* @param {*} err - error to set.
* @param {*} val - value to set.
* @class AsyncResult
*/
const AsyncResult = function(err, val) {
this.set(err, val);
};

AsyncResult.prototype = {
/**
* Indicates if there is an error.
* @returns {boolean} - True if there is an error or rejected promise, otherwise false
*/
isError() {
return this.err() != null;
},
/**
* Indicates if there is no error.
* @returns {boolean} - True if there is no error, otherwise false
*/
isOk() {
return this.err() == null;
},

/**
* Indicates if there is no error and value is significant.
* Empty string treated as significant while `undefined` and `null` are not.
* @returns {boolen} - True if there is no error and value is not empty
*/
isEmpty() {
return this.isOk() && !this.hasValue();
},

/**
* Indicates if a value is significant.
* Empty string treated as significant while `undefined` and `null` are not.
* @returns {boolen} - True if value is not empty
*/
hasValue() {
return this.val() != null;
},

//#region extracting values

/**
* returns error
* @returns {*} - error
*/
err() {
return this.error;
},
/**
* returns value
* @returns {*} - value
*/
val() {
return this.value;
},
/**
* returns error if any or value
* @returns {*} - error or value in such order.
*/
errOrVal() {
return this.isError() ? this.err() : this.val();
},
Expand All @@ -32,12 +70,28 @@ AsyncResult.prototype = {

//#region setting values

/**
* Sets the value.
* @param {*} value - value to set
* @return {void}
*/
setValue(value) {
this.value = value;
},
/**
* Sets the error.
* @param {*} err - error to set
* @return {void}
*/
setError(err) {
this.error = err;
},
/**
* Sets both, error and value
* @param {*} err - error to set
* @param {*} value - value to set
* @return {void}
*/
set(err, value) {
this.setError(err);
this.setValue(value);
Expand All @@ -46,12 +100,25 @@ AsyncResult.prototype = {
//#endregion
};

AsyncResult.success = function(data) {
return new this(null, data);
};
/**
* Creates success AsyncResult instance with given value
* @param {*} value - value to set.
* @returns {AsyncResult} success AsyncResult instance
*/
function success(value) {
return new this(null, value);
}

AsyncResult.fail = function(err) {
/**
* Creates failed AsyncResult instance with given error.
* @param {*} err - error to set
* @returns {AsyncResult} failed AsyncResult instance
*/
function error(err) {
return new this(err);
};
}

AsyncResult.success = success;
AsyncResult.fail = error;

export default AsyncResult;
1 change: 1 addition & 0 deletions config.js
@@ -1,4 +1,5 @@
import AsyncResult from './asyncResult';
export default {
/** the Default AsyncResult class, feel free to override */
AsyncResult
};
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "asyncresult-js",
"version": "1.1.1",
"version": "1.1.2",
"description": "helpful wrapper for promises",
"main": "index.js",
"repository": "https://github.com/taburetkin/asyncresult.git",
Expand Down
46 changes: 46 additions & 0 deletions test/unit/utils.spec.js
Expand Up @@ -390,6 +390,9 @@ describe('# utils:', function() {
},
baz() {
return Promise.resolve('baz');
},
test() {
return this;
}
};
afterEach(function() {
Expand Down Expand Up @@ -432,5 +435,48 @@ describe('# utils:', function() {
expect(result.err()).to.be.equal('foo');
});
});

describe('when applying to prototype', function() {
let Cls;
let instance;
beforeEach(() => {
Cls = function() {};
Cls.prototype = Object.assign({}, context);
addAsync(Cls.prototype, ['test'], { context: null });
instance = new Cls();
});
it('should bind context correctly', async () => {
let result = await instance.testAsync();
expect(result, 'be AsyncResult').to.be.instanceOf(AsyncResult);
expect(result.val(), 'be instance').to.be.equal(instance);
});
});

describe('when applying to class', function() {
let Cls;
let instance;
beforeEach(() => {
Cls = function() {};
Cls.staticTest = function() { return this; };
Cls.prototype = Object.assign({}, context);
addAsync(Cls, ['test']);
addAsync(Cls, ['staticTest'], { static: true });
instance = new Cls();
});
it('should bind context correctly', async () => {
let result = await instance.testAsync();
expect(Cls.testAsync).to.be.undefined;
expect(result, 'be AsyncResult').to.be.instanceOf(AsyncResult);
expect(result.val(), 'be instance').to.be.equal(instance);
});
it('should use class static methods if option `static` is true', async () => {
expect(Cls.staticTestAsync).to.be.a('function');
let result = await Cls.staticTestAsync();
expect(result).to.be.instanceOf(AsyncResult);
expect(result.val()).to.be.equal(Cls);
});
});


});
});
29 changes: 28 additions & 1 deletion utils.js
@@ -1,5 +1,14 @@
import config from './config';

/**
* Converts given argument to a Promise resolved with AsyncResult instance.
* In case argument is an Error AsyncResult instance will be created with error.
* In case argument is a promise which wiil be rejected will create AsyncResult with error
*
* @param {*} promise - any argument
* @param {*} [OwnAsyncResult] - own AsyncResult class, if omitted will be used config's version
* @returns {Promise} - promise which will be resolved with AsyncResult
*/
function toAsyncResult(promise, OwnAsyncResult) {
let AsyncResult = OwnAsyncResult || config.AsyncResult;

Expand Down Expand Up @@ -31,6 +40,12 @@ function toAsyncResult(promise, OwnAsyncResult) {
return newPromise;
}

/**
* Wraps given method, returned result of a method will be instance of AsyncResult
* @param {function} method - any method you wants to wrap
* @param {object} [options] - You may specify context and AsyncResult for new method through options
* @return {function} - wrapped method
*/
function wrapMethod(method, { context, AsyncResult } = {}) {
if (typeof method !== 'function') {
throw new Error('first argument should be a function');
Expand All @@ -51,13 +66,25 @@ function wrapMethod(method, { context, AsyncResult } = {}) {
return asyncMethod;
}

/**
* Adds async versions of provided methods
* @param {(Object|Class)} context - object literal or class
* @param {(string|string[])} methodName - method name(s) of provided object.
* In case context is a class and there is no option `static:true` will use prototype
* @param {object} options - options. context, static
* @return {void}
*/
function addAsync(context, methodName, options = {}) {
if (Array.isArray(methodName)) {
for (let x = 0; x < methodName.length; x++) {
addAsync(context, methodName[x], options);
}
} else {
if (options.context == null) {
let isCtor = typeof context === 'function';
if (isCtor && !options.static) {
context = context.prototype
}
if (!isCtor && options.context === void 0) {
options.context = context;
}
context[methodName + 'Async'] = wrapMethod(context[methodName], options);
Expand Down

0 comments on commit b537069

Please sign in to comment.