Results
Discriminated Unions including Maybe (an option type) and Result for javascript with fewer bugs
Install / Use
/learn @uniphil/ResultsREADME
⚠️ Consider alternatives ⚠️
You may want to consider tools like TypeScript, which can now cover most of the use cases for results. (IMO less ergonomically in some ways, but, :shrug:)
While short of what I might call "complete", I do still use this on projects where I just want to write reasonable JS code that runs directly in the browser. It's been pretty solid, and I'll probably continue using it! But it's unlikely to change much. So, use it or don't, but don't expect much evolution beyond bug-fixes :)
Results 
Results is a tiny library bringing Discriminated Unions (aka Sum Types or Algebraic Types) to JavaScript, with match for better program flow control.
Results ships with full-featured Maybe (sometimes called an Option Type) and Result unions built-in, helping you safely deal with optional data and error handling.
The goal of Results is JavaScript with fewer bugs.
Install
$ npm install results
Quickstart
Result for error handling
import { Result, Ok, Err } from 'results';
function validateNumber(number) {
if (isFinite(number)) {
return Ok(number);
} else {
return Err(`expected a finite number but got '${number}' (a '${typeof number}')`);
}
}
function computeSum(numbers) {
if (!(numbers instanceOf Array)) {
return Err(`expected an Array but got '${numbers}' (a '${typeof numbers}')`);
}
return Result.all(numbers.map(validateNumber))
.andThen(nums => nums.reduce((a, b) => a + b));
}
// Since computeSum returns a Result (eiter an Err() or an Ok()), we can match
// for it and handle all possible cases:
Result.match(computeSum([1, 2, 3, 4, -5]), {
Ok: sum => console.log(`The sum is: ${sum}`),
Err: err => console.error(`Something went wrong: ${err}`)
});
// Result is a synchronous compliment to Promise, and plays nicely with it:
fetch('http://example.com/numbers')
.then(resp => resp.json())
.then(nums => computeSum(nums).toPromise())
.then(sum => console.log(`The sum is: ${sum}`))
.catch(err => console.error(`Something went wrong: ${err}`));
Maybe for nullable references
import { Maybe, Some, None } from 'results';
// Take a tree of Maybe({val: any, left: Maybe, right: Maybe}) and flatten it
// into an array of values:
function flattenDepthFirst(root) {
return Maybe.match(root, {
None: () => [],
Some: node => [node.val]
.concat(flattenDepthFirst(node.left))
.concat(flattenDepthFirst(node.right))
});
}
Maybe for default values and possibly-undefined return values
import { Maybe, Some, None } from 'results';
function printGreeting(name) {
// get the name, or set a default if name is None()
const nameToPrint = Maybe.match(name, {
Some: n => n,
None: () => 'friend'
});
console.log(`Hello, oh wonderful ${nameToPrint}!`);
}
// The Maybe union has helpful methods, like .unwrapOr for getting the value
// with a default:
function printGreeting(name) {
const nameToPrint = name.unwrapOr('friend');
console.log(`Hello, oh wonderful ${nameToPrint}!`)
}
// For functions whose result may not be defined, using Maybe encourages the
// caller to handle all cases
function get(obj, key) {
if (obj.hasOwnProperty(key)) {
return Some(obj[key]);
} else {
return None();
}
}
Union as a powerful Enum
import { Union } from 'results';
const HTTPVerbs = Union({
Options: {}, // the {} values are just placeholders, only the keys are used
Head: {},
Get: {},
Post: {},
Put: {},
Delete: {}
}, {
// the optional second object parameter to Union creates prototype methods:
isIdempotent() {
return HTTPVerbs.match(this, {
Post: () => false,
_: () => true // "_" is reserved as a catch-all in match
});
}
});
let myVerb = HTTPVerbs.Get();
console.log(`Get ${myVerb.isIdempotent() ? 'is' : 'is not'} idempotent.`);
// => "Get is idempotent"
myVerb = HTTPVerbs.Post();
console.log(`Post ${myVerb.isIdempotent() ? 'is' : 'is not'} idempotent.`);
// => "Post is not idempotent"
HTTPVerbs.match(myVerb, {
Delete: () => console.warn('some data was deleted!'),
_: () => null
});
While there is nothing react-specific in Results, it does enable some nice patterns:
import React from 'react';
import { Union } from 'results';
const AsyncState = Union({
Pending: {},
Success: {},
Failed: {}
});
class Spinner extends React.Component {
static propTypes = {
reqState: React.PropTypes.instanceOf(AsyncState.OptionClass)
}
render() {
return AsyncState.match(this.props.reqState, {
Pending: loaded => (
<div className="spinner overlay spinning">
<div className="spinner-animation">
Loading {loaded}%...
</div>
</div>
),
Failed: errMsg => (
<div className="spinner overlay failed">
<div className="spinner-err-message">
<h3>Failed to load :( </h3>
<p>{errMsg}</p>
</div>
</div>
),
Success: <div style={{display: 'none'}}></div>
});
}
}
API
Union(options[, proto[, static_[, factory]]])
Creates a discriminated union with members specified in the options object.
Returns a union object.
-
optionsAn object defining the members of the set. One member is added for each key ofoptions, and the values are ignored. Almost any name can be used for the members except for two reserved names:toString, which is automatically added for nicer debugging, andOptionClass, which is used to attach the constructor function for member instances, for typechecking purposes. Union() will throw if either of those names are used as members inoptions.
Maybe.None()is an example of a member added viaoptions. -
protowill be used to set the protoype of member instances.toStringwill automatically be added to the prototype by default, but if you define it inprotoit will override the built-in implementations.Result.Ok(1).toPromise()is an example of a method attached throughproto. -
static_likeprotobut for the object returned byUnion(): functions defined here can inspect the union, like accessingthis.OptionClass. By default,toStringis added for you, but defining it instatic_will override the default implementation. Union() will throw if a key instatic_already exists inoptions.Result.all()is an example of a function attached throughstatic_. -
factoryis not stable and should not be considered part of the public API :) It is used internally byMaybeandResult, check the source if you want to get down and dirty.
Union.is(first, second)
Deeply checks two union option members. This passes if:
firstandsecondare strictly equal (===), or- They are instances of the same
UnionOptionClass, and- They are the same member of the
UnionOptionClass, and - Each matching payload parameter satisfies:
- A recursive check of equality as defined by
Union.is
- A recursive check of equality as defined by
- They are the same member of the
- or they both implement
.valueOfwhich passes strict equality, or - they both implement
.equalsandfirst.equals(second)
These criteria and the implementation are <del>stolen</del> borrowed from
Immutable, and
in fact results's equality checks are compatible with Immutable's. Nesting
Immutable collections in OptionClassInstances, and nesting
OptionClassInstance in immutable collections are both supported.
This compatibility is totally decoupled from immutablejs -- results has no
dependency on immutable whatsoever.
union object
Created by Union(), this is an object with a key for each member of the union,
plus anything attached via static_, which include OptionClass and toString
by default. It is not safe to iterate the keys of a union object.
Each member name's key maps to a factory to create a member instance, from a
constructor called OptionClass (whose reference is also attached to the
union object via they key "OptionClass").
match(option, paths) static method on union object
Automatically attached to every union object, .match is a better way to
control program flow depending on which member of Union you are dealing with.
-
optionthe OptionClass instance to match against, likeSome('hi')orErr(new Error(':(')). Ifoptionis not an instance of the union'sOptionClass,matchwill throw. -
pathsan object, mapping member names to callback functions. The object must either exhaustively cover all members in the Union with callbacks, or map zero or more members to callbacks and provide a catch-all callback for the name'_'. If the coverage is not exhaustive, or if unrecognized names are included as keys,.matchwill throw.
.match will synchronously call the matching callback and return its result,
passing all arguments given to the Union Option as arguments to the callback.
import { Union } from 'results';
const Stoplight = Union({ // Union(), creating a `union` object called StopLight.
Red: {},
Amber: {},
Green: {}
});
Stoplight.match(Stoplight.Green(), {
Red: () => console.error('STOP!!!'),
Amber: () => console.warn('stop if you can'),
Green: () => console.info('ok, continue')
});
options static property on union object
After creating a union object, the .options property references an object
containing keys for each union option specified. It's not usually that useful
unless you want to introspect the union and see what options it has -- powerful,
but usually not necessary!
OptionClass() constructor
A function for creating Op
Related Skills
node-connect
343.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
90.0kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
343.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
343.1kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
