Giter Club home page Giter Club logo

result-js's Introduction

result-js

Greenkeeper badge Rusty Monad Results for JS

Build Status Coverage Status

Standard JS error management is done via throw and try..catch statements. They, however, pose the risk that error opportunities are not handled by the user of an API, hence pose the risk of fatal runtime errors.

In order to make a user aware of the risk of failure, monad error management was created. One of the most prominent usages of such a result system is core::result::Result of the programming language Rust.

This module is based on the Rust implementation, but brings some changes in order to better use it in JS. One of the major differences is that result-js uses lowerCamelCase instead of snake_case. Additionally, the methods ok(), err() and unwrapOrDefault() were not implemented, since the former require a Option-dependency and the latter can easily be worked around by providing a default value yourself (using Result.or()).

result-js does not have any dependencies and conducts unit tests and coverage with TAP and coveralls.

You can find the complete API, as defined in code, below the examples!

Installation

npm i result-js --save

Simple Example

In the following example, you can see that the traditional way needs a lot more LoC, the type of myResult is not pinned and you might forget to use try..catch. The Result Monad helps to clean this mess up!

For more simple examples, please take a look at ./test.js, on which Travis CI and Coveralls tests are based!

'use strict';

// Traditional way

const syncButMightFail = () => {

  throw 'NaY!';
};

let myResult = 'YaY';
try {
  myResult = syncButMightFail();
}
catch($e) {
  console.log($e.toString()); // don't even care...
}


// -------------------------------------
// With Result


const Result = require('result-js');

const syncButMightFail = () => {

  return Result.fromError('NaY!');
};

const myResult = syncButMightFail().or('YaY');

Usage

Create new Result

'use strict';

const Result = require('result-js');

const resultOk = Result.fromSuccess('YaY');
const resultErr = Result.fromError('NaY');

// ...

Register Ok() and Err()

// ...

// After the following line, Ok() and Err() will be available on a global level.
// That means that you can do stuff, like
//     return Ok(val);
//     return Err('failed!');
Result.registerGlobals();

// ...

Check if Error

// ...

if (resultOk.isOk() || !resultOk.isErr()) { console.log('Result is OK; this will be visible!'); }
if (resultErr.isOk() || !resultErr.isErr()) { console.log('Result is OK; this will _not_ be visible!'); }

Get Value

// ...

// `unwrap` will throw if the Result is an Error
var myResult = resultOk.unwrap();

// `expect` will also throw if the Result is an Error, but add a message
myResult = resultOk.expect('uh oh!');

// `and` will return the passed value instead of the Ok-value if the Result is not an error
myResult = resultOk.and('SURPRISE!');
myResult = resultOk.andThen(res => res + ' for Result!');


// `or` will return the passed value instead of the Result's error value in case the Result was an error
myResult = resultErr.or('no error any more!');
myResult = resultErr.orElse(err => new Error(err));

// Or just a very simple match, just what you already know from then-ables, like Promises
resultErr.match(okVal => {
  console.log('Since we use the `resultErr` Result, this message will never be visiable!');
}, errVal => {
  console.log('This message will be visible! The error is: ' + errVal);
});

Control Flow

// ...

console.log(
  resultOk.andThen(() => Ok(2)).andThen(val => val * val)
);
// -> 4


console.log(
  resultErr.orElse(() => Ok(3)).andThen(val => val * val)
);
// -> 9

API

All methods work just as described in the Rust documentation. The interface below includes Exceptions, however all methods are fully implemented and will not throw. The Exceptions are in place in order to provide you a clear, non-cluttered API overview.

/**
 * Rusty Result wrapper
 *
 * @type {Result}
 */
module.exports = class Result {
    /**
     * Create success Result with a return value.
     *
     * @param {*} val
     * @returns {Result}
     */
    static fromSuccess(val) { throw new Error ('Not Implemented: Result.fromSuccess'); };

    /**
     * Create error Result with a return value.
     *
     * @param {*} err
     * @returns {Result}
     */
    static fromError(err) { throw new Error ('Not Implemented: Result.fromError'); };

    /**
     * Similar to Rust's `try!`, but only returns a {Result} to the caller
     *
     * @param {function} fun Function to execute
     * @returns {Result}
     */
    static fromTry(fun) { throw new Error ('Not Implemented: Result.fromTry'); };

    /**
     * Register global convenience-functions Ok() and Err()
     */
    static registerGlobals() { throw new Error ('Not Implemented: Result.registerGlobals'); };

    /**
     * Returns true if the result is Ok.
     *
     * @returns {boolean}
     */
    isOk() { throw new Error ('Not Implemented: Result.isOk'); };

    /**
     * Returns true if the result is Err.
     *
     * @returns {boolean}
     */
    isErr() { throw new Error ('Not Implemented: Result.isErr'); };

    /**
     * Maps a Result<T, E> to Result<U, E> by applying a function to a contained Ok value, leaving an Err value untouched.
     * This function can be used to compose the results of two functions.
     *
     * @param {function} op
     * @returns {Result}
     */
    map(op) { throw new Error ('Not Implemented: Result.map'); };

    /**
     * Maps a Result<T, E> to Result<T, F> by applying a function to a contained Err value, leaving an Ok value untouched.
     * This function can be used to pass through a successful result while handling an error.
     *
     * @param {function} op
     * @returns {Result}
     */
    mapErr(op) { throw new Error ('Not Implemented: Result.mapErr'); };

    /**
     * Returns an iterator over the possibly contained value.
     * The iterator yields one value if the result is Ok, otherwise none.
     *
     * @returns {Iterable.<*>}
     */
    iter() { throw new Error ('Not Implemented: Result.iter'); };

    /**
     * Returns `Ok(val)` if the result is Ok, otherwise returns `Err(err)` of itself.
     *
     * @param {*} val
     * @returns {Result}
     */
    and(val) { throw new Error ('Not Implemented: Result.and'); };

    /**
     * Calls `resultEmitter` if the result is Ok, otherwise returns `Err(err)` value of itself.
     * This function can be used for control flow based on Result values.
     *
     * @param {ResultEmitter} resultEmitter
     * @returns {Result}
     */
    andThen(resultEmitter) { throw new Error ('Not Implemented: Result.andThen'); };

    /**
     * Returns `Ok(val)` if the result is Err, otherwise returns `Ok(ok)` of itself.
     *
     * @param {*} val
     * @returns {Result}
     */
    or(val) { throw new Error ('Not Implemented: Result.or'); };

    /**
     * Calls `resultEmitter` if the result is Err, otherwise returns the `Ok(ok)` value of itself.
     * This function can be used for control flow based on result values.
     *
     * @param {ResultEmitter} resultEmitter
     * @returns {Result}
     */
    orElse(resultEmitter) { throw new Error ('Not Implemented: Result.orElse'); };

    /**
     * Unwraps a result, yielding the content of an Ok.
     *
     * @throws if the value is an Err, with a message provided by the Err's value.
     * @returns {*}
     */
    unwrap() { throw new Error ('Not Implemented: Result.unwrap'); };

    /**
     * Unwraps a result, yielding the content of optb. Else it throws.
     *
     * @param {*} optb
     * @return {*}
     */
    unwrapAnd(optb) { throw new Error ('Not Implemented: Result.unwrapAnd'); };

    /**
     * Unwraps a result, calling valEmitter with its value. If the value is an Err then it throws.
     *
     * @param {ValueEmitter} valEmitter
     * @return {*}
     */
    unwrapAndThen(valEmitter) { throw new Error ('Not Implemented: Result.unwrapAndThen'); };

    /**
     * Unwraps a result, yielding the content of an Err.
     *
     * @throws if the value is an Ok, with a custom panic message provided by the Ok's value.
     * @returns {*}
     */
    unwrapErr() { throw new Error ('Not Implemented: Result.unwrapErr'); };

    /**
     * Unwraps a result, yielding the content of an Ok. Else it returns optb.
     *
     * @param {*} optb
     * @return {*}
     */
    unwrapOr(optb) { throw new Error ('Not Implemented: Result.unwrapOr'); };

    /**
     * Unwraps a result, yielding the content of an Ok. If the value is an Err then it calls valEmitter with its value.
     *
     * @param {ValueEmitter} valEmitter
     * @return {*}
     */
    unwrapOrElse(valEmitter) { throw new Error ('Not Implemented: Result.unwrapOrElse'); };

    /**
     * Unwraps a result, yielding the content of an Ok.
     *
     * @throws if the value is an Err, with a message including the passed message, and the content of the Err.
     * @param {String} msg
     * @returns {*}
     */
    expect(msg) { throw new Error ('Not Implemented: Result.expect'); };

    /**
     * Unwraps a result, yielding the content of an Err.
     *
     * @throws if the value is an Ok, with a panic message including the passed message, and the content of the Ok.
     * @param {String} msg
     * @returns {*}
     */
    expectErr(msg) { throw new Error ('Not Implemented: Result.expectErr'); };

    /**
     * JS convenience then-like handler (sync)
     *
     * @param {ResultHandler} okHandler
     * @param {ResultHandler} errHandler
     */
    match(okHandler, errHandler) { throw new Error ('Not Implemented: Result.match'); };

    /**
     * JS convenience method to handle a result NodeJS-style
     * Example:
     * Result.fromError('uh oh!').node((err, val) => {
     *   // do sth.
     * });
     *
     * @param {NodeJSStyleHandler} handler
     */
    node(handler) { throw 'Not Implemented: Result.node'; };
};


/**
 * This Callback is used to produce a final Result
 *
 * @callback ResultEmitter
 * @param {*} val
 *   `val` will contain the result of {Result}.
 * @returns {*}
 */

/**
 * This Callback is used to produce a final Value
 *
 * @callback ValueEmitter
 * @param {*} val
 *   `val` will contain the result of {Result}.
 * @returns {*}
 */

/**
 * This Callback is used as a return-handler
 *
 * @callback ResultHandler
 * @param {*} ret
 *   `ret` will either contain the result or the error, depending on the parameter position of the callback
 */

/**
 * This callback is used as NodeJS-style handler
 *
 * @callback NodeJSStyleHandler
 * @oaram {*} err null if no error occurred
 * @param {*} val null if an error occurred
 */

result-js's People

Contributors

greenkeeper[bot] avatar minecrawler avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

result-js's Issues

Consume objects

In Rust, Result objects are usually consumed by certain methods. That behavior should be simulated by adding a consumed field of type bool. If that flag is set, an Error will be thrown (cannot use consumed objects). Also, the API will create new objects instead of modifying the current one.

Imho, this could be done with a wrapper. That would allow safe development and performance in production (given TDD and unit tests are performed)

Throw "NotImplementedErrors" in "interface/result.h.js"

To prevent the error source of #3 I would advise you to throw "NotImplementedErrors" in the empty functions of "interface/result.h.js".

Such custom errors can be easily created like:

class NotImplementedError extends Error {
  constructor(message) {
    super(message);
    this.name = 'NotImplementedError';
  }
}

And are used as follows:

module.exports = class Result {
  // ...
  static fromSuccess(val) { throw new NotImplementedError() };
  // ...
}

Cannot resolve or reject Promise

Given the following code

const Result = require('result-js');
Promise.resolve(Result.fromSuccess('*'));

I receive a Promise { <pending> }, which is quite hard to debug, because wtf?

What could trigger the described behavior? Maybe, the Promise thinks, Result is another Promise? However, Result is not a Promise, hence cannot resolve, so it will never ever resolve?

use internal state(?)

At the moment, the lib deduces if an Error happened from the content of the _err field. That's nice, because it allows other libraries to play around with the internals easily.

However, I see a clash with the Rust philosophy here. What if 3rd party code messes up the internal state because of that? It would lead to stupid errors which are hard to debug and no one wants to debug other libraries. In Rust, something like that is not possible, however, we are in the JS world!

As a result, I am thinking about hiding the internals away, such that it is not easy to change them (via Symbols, which have to be derived from a file in the Result project). That's not too easy and feels really hacky and awkward. I think, that's a better idiot-protection than just using the underscore-name.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.