npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

r-result

v1.5.1

Published

Rust's Result in JS

Downloads

62

Readme

Travis Status NPM Downloads Version ISC Licensed Github Issue Count Github Stars

Rust's Result implemented in JS.

npm install r-result

Usage

See Rust's Result.

Example

For a real example, see tennu-factoids.

// Using EcmaScript 6 features in this example. ES6 not required to use package.
// This module exports a single function that asks the user for a prime number,
// and returns them a string about their input.

const {Ok, Fail} = require("r-result");
const {format} = require("util");
const {promptUser, alertUser} = require("...");

// Some reasons that we fail.
const nan = Symbol("NaN");
const inf = Symbol("Inf")
const nonPrime = Symbol("Not Prime");
const neg = Symbol("Negative Number");
const nonInt = Symbol("Not an Integer");
const zero = Symbol("Zero");

const getNumberFromUser = function (requestString) {
    // Assume blocking prompt. Don't want to complicate with promises.
    const userInput = promptUser(requestString);

    const number = Number(userInput);

    // While it isn't necessary to show all of these cases for showing how to use Result,
    // I just wanted to show you some of the difficulties you will encounter when trying
    // to deal with user input of numbers in JavaScript.
    //
    // The Infinity & NaN checks are redundant  with the isInteger check, except we want
    // to show a different error message to the user in these cases.
    if (Number.isNaN(userInput) {
        return Fail(nan);
    } else if (number === Infinity) {
        return Fail(inf);
    } else if (!Number.isInteger()) {
        return Fail(nonInt);
    } else if (Math.abs(number) !== number) {
        return Fail(neg);
    } else if (number === 0) {
        return Fail(zero);
    } else {
        return Ok(number);
    }
};

const getPrimeNumberFromUser = function () {
    return getNumberFromUser("Prime number please!")
    .andThen(function (number) {
        if (isPrime(number)) {
            return Ok(number);
            // You could also just `return this` since you're not transforming the value.
        } else {
            return Fail(nonPrime);
        }
    });
};

const doGetPrimeNumberFromUser = function () {
    const reply = getPrimeNumberFromUser().match({
        Ok(number) { 
            return `Indeed! ${number} is prime!`;
        },

        Fail(reason) {
            switch (reason) {
                case nan:      return "That wasn't a number.",
                case inf:      return "Sneaky user, trying to throw me into an infinite loop with Infinity.",
                case nonInt:   return "Sneaky user, trying to give me a rational number instead of an integer.",
                case neg       return "Sneaky user, trying to give me a negative number...",
                case nonPrime: return "Sorry, but that number isn't prime.",
                default:       throw new Error(`Unhandled failure reason: ${reason}`)
            }
        }
    });
    
    alertUser(reply);
};

module.exports = doGetPrimeNumberFromUser;

Differences

Obviously, JavaScript is a different language than Rust. Differences in idiomatic code and type systems means there will be differences in the code and API.

We already have Errors in JavaScript. This means we can't use the Err name and so instead use the name Fail.

Because idiomatic JS uses camelCase instead of lower_case, all method names have been translates to camelCase.

Because we cannot get a slice, but we can get an array, as_slice() has been changed to toArray(). Not sure if it'll actually be helpful though, unless you flatmap over an array of results. But it's there for completeness.

The Rust reference type specific methods (e.g. as_mut_slice) are obviously not present.

The methods and, or, andThen, orElse are less strict about the types of the parameters they take. We do not check to make sure you called us with the correct type. If you want a stricter version of these that does check the type (at a small performance penalty), please file a bug or send a Pull Request. Otherwise, for best results, please pass values of the same type as what Rust says.

We don't have a blessed Option type, so instead of returning one, .ok() and .fail() are instead doing what Result::unwrap and Result::unwrap_err are doing and throwing an error if the value isn't the right type. By default, they report a generic error message, but you can give a specific error message as the optional argument.

In Rust, the Result must be used. Not using a returned Result is a compile time error. In JavaScript, we have no way of guaranteeing this, so not using a Result is entirely possible.

Because JavaScript doesn't have a match expression or even a match statement, we provide a match method that takes an object with functions Ok and Fail.

The Debug trait doesn't exist in JS, so there's a debug method directly in the implementation.

There's also debugOk and debugFail methods that inspect the inner values if the result matches that variant or does nothing otherwise, and then returns the result. These don't have an analog to Rust's standard library, but are useful for e.g. assert(result.debugFail(logfn).isOk());

Functional Variants

Every method has a function that takes this as the first parameter as a function on the module.

This lets you write in an Erlang/Elixir style or just pass things to other functions.

const Result = require('r-result');
const sampleResult = Result.Ok(true);
Result.map(sampleResult, function (value) {
    assert(value === true);
});
const Result = require("r-result");
const Ok = Result.Ok;
const Fail = Result.Fail;

// Unrelated: You can totes have a Result<undefined, string> if there's no good value for Ok.
const someResults = [Ok(), Ok(), Ok(), Fail("unexpected spanish inquisition"), Ok()];
const resultOfSomeResults = someResults.reduce(Result.and);

API

Syntax

  • :: means 'on the prototype' of the value.
  • <> means generic parameters. T is used for an Ok value. F is used for a Failure. T', F' are for a second Ok/Failure type, though they may (and in most cases, should be) the same value as the non-prime variant. _ means "any type" or "no type", since it can be anything without issue.
  • [T, 0...1] means an array of type T with a length of either 0 or 1.
  • | means either the type on the left or the type on the right.
  • InspectOpts is the options object you pass to util.inspect.

API

  • Result.Ok(value: T) -> Result<T, _>
  • Result.Fail(value: F) -> Result<_, F>
  • Result<T, F>::ok() -> T | throw TypeError
  • Result<T, F>::ok(errorMessage: String) -> T | throw TypeError
  • Result<T, F>::fail() -> F | throw TypeError
  • Result<T, F>::fail(errorMessage: String) -> F | throw TypeError
  • Result<T, F>::isOk() -> Boolean
  • Result<T, F>::isFail() -> Boolean
  • Result<T, F>::map(mapper: function (value: T) -> T') -> Result<T', F>
  • Result<T, F>::mapFail(mapper: function (failure: F) -> F' -> Result<T, F'>
  • Result<T, F>::and(otherResult: Result<T', F'>) -> Result<T', F | F'>
  • Result<T, F>::or(otherResult: Result<T', F'>) -> Result<T | T', F'>
  • Result<T, F>::andThen(monadic_mapper: function (value: T) -> Result<T', F'>) -> Result<T', F | F'>
  • Result<T, F>::orElse(monadic_mapper: function (failure: F) -> Result<T', F'>) -> Result<T | T', F'>
  • Result<T, F>::toArray() -> [T; 0...1]
  • Result<T, F>::unwrapOr(defaultValue: T') -> T | T'
  • Result<T, F>::unwrapOrElse(defaultValueMaker: function (failure: F) -> T') -> T | T'
  • Result<T, F>::match({Ok: Fn(value: T) -> void, Fail: Fn(failure: F) -> void}) -> void
  • Result<T, F>::debug(logfn: Fn(debugString: String) -> void) -> void
  • Result<T, F>::debug(logfn: Fn(debugString: String, inspectOpts: InspectOpts)) -> void
  • Result<T, F>::debugOk(logfn: Fn(debugString: String) -> void) -> void
  • Result<T, F>::debugOk(logfn: Fn(debugString: String, inspectOpts: InspectOpts)) -> void
  • Result<T, F>::debugFail(logfn: Fn(debugString: String) -> void) -> void
  • Result<T, F>::debugFail(logfn: Fn(debugString: String, inspectOpts: InspectOpts)) -> void

Rationale and Rant on Error Handling

Author: Havvy

You might be looking at this, and thinking that this just reimplements error handling, and yes, this is true. The difference though, is that by being a return value it is made explicitly clear that the function does not always succeed even for all valid inputs to the function. This explicitness also means that you are not forcing your function caller to use try-catch to capture all of the return values. Try-catch becomes an error handling mechanism that should only be used for actual errors, whether they be programmer induced errors or some fundamental assumption actually changed that your program cannot handle. This means that most code doesn't have try/catch in it, since the these errors generally cannot be handled except to report them to the user and log them.

Combined with the usage of some Future<T, E> value (like promises), and the amount of real error handling code you'll write drops dramatically. If you're ever writing if (err) { throw err; }, you're using a poor abstraction that makes you manually propogate errors.

Likewise, just as you should never throw instead of return Fail(...), when dealing with promises, you should never reject(...) when you can resolve(Fail(...)). The only time you should use reject is when you'd use throw in synchronous code.

Some programmers actually disagree with this sentiment, trying to conflate reject to mean both return Fail(...) and throw new Error, using something like bluebird's OperationalError for the Fail case. The author of this package disagrees with this approach because different concerns should be handled differently. Those who disagree with the author say that not handling Failures is an error, and so should be wrapped in an error to force the end programmer to deal with it. This does sound like a good idea (and you'll notice that in Rust it's a compile time error to not handle a Result), but you trade off readability and speed (making a stacktrace is not cheap). Should you really need to make sure your user does something with failures, making them errors is a heavy-handed approach, and it does work.