A lightweight object defining how to handle each variant of a
Result
.
The behavior of this type is checked by TypeScript at compile time, and bears no runtime overhead other than the very small cost of the container object.
You can use the discriminant via the variant
property of Result
instances
if you need to match explicitly on it.
You can think of this like a short-circuiting logical "and" operation on a
Result
type. If result
is Ok
, then the result is the
andResult
. If result
is Err
, the result is the Err
.
This is useful when you have another Result
value you want to provide if and
only if* you have an Ok
– that is, when you need to make sure that if you
Err
, whatever else you're handing a Result
to also gets that Err
.
Notice that, unlike in map
or its variants, the original result
is
not involved in constructing the new Result
.
import { and, ok, err, toString } from 'true-myth/result';
const okA = ok('A');
const okB = ok('B');
const anErr = err({ so: 'bad' });
console.log(toString(and(okB, okA))); // Ok(B)
console.log(toString(and(okB, anErr))); // Err([object Object])
console.log(toString(and(anErr, okA))); // Err([object Object])
console.log(toString(and(anErr, anErr))); // Err([object Object])
The type of the value wrapped in the Ok
of the Result
.
The type of the value wrapped in the Ok
of the andResult
,
i.e. the success type of the Result
present if the checked
Result
is Ok
.
The type of the value wrapped in the Err
of the Result
.
The Result
instance to return if result
is Err
.
The Result
instance to check.
Apply a function to the wrapped value if Ok
and return a new Ok
containing the resulting value; or if it is Err
return it
unmodified.
This differs from map
in that thenFn
returns another Result
.
You can use andThen
to combine two functions which both create a Result
from an unwrapped type.
You may find the .then
method on an ES6 Promise
helpful for comparison: if
you have a Promise
, you can pass its then
method a callback which returns
another Promise
, and the result will not be a nested promise, but a single
Promise
. The difference is that Promise#then
unwraps all layers to only
ever return a single Promise
value, whereas Result.andThen
will not unwrap
nested Result
s.
This is is sometimes also known as bind
, but not aliased as such because
bind
already means something in JavaScript.
import { ok, err, andThen, toString } from 'true-myth/result';
const toLengthAsResult = (s: string) => ok(s.length);
const anOk = ok('just a string');
const lengthAsResult = andThen(toLengthAsResult, anOk);
console.log(toString(lengthAsResult)); // Ok(13)
const anErr = err(['srsly', 'whatever']);
const notLengthAsResult = andThen(toLengthAsResult, anErr);
console.log(toString(notLengthAsResult)); // Err(srsly,whatever)
The type of the value wrapped in the Ok
of the Result
.
The type of the value wrapped in the Ok
of the Result
returned by the thenFn
.
The type of the value wrapped in the Err
of the Result
.
The function to apply to the wrapped T
if maybe
is Just
.
The Maybe
to evaluate and possibly apply a function to.
Allows you to apply (thus ap
) a value to a function without having to take
either out of the context of their Result
s. This does mean that
the transforming function is itself within a Result
, which can be hard to
grok at first but lets you do some very elegant things. For example, ap
allows you to do this (using the method form, since nesting ap
calls is
awkward):
import { ap, ok, err } from 'true-myth/result';
const one = ok<number, string>(1);
const five = ok<number, string>(5);
const whoops = err<number, string>('oh no');
const add = (a: number) => (b: number) => a + b;
const resultAdd = ok<typeof add, string>(add);
resultAdd.ap(one).ap(five); // Ok(6)
resultAdd.ap(one).ap(whoops); // Err('oh no')
resultAdd.ap(whoops).ap(five) // Err('oh no')
Without ap
, you'd need to do something like a nested match
:
import { ok, err } from 'true-myth/result';
const one = ok<number, string>(1);
const five = ok<number, string>(5);
const whoops = err<number, string>('oh no');
one.match({
Ok: n => five.match({
Ok: o => ok<number, string>(n + o),
Err: e => err<number, string>(e),
}),
Err: e => err<number, string>(e),
}); // Ok(6)
one.match({
Ok: n => whoops.match({
Ok: o => ok<number, string>(n + o),
Err: e => err<number, string>(e),
}),
Err: e => err<number, string>(e),
}); // Err('oh no')
whoops.match({
Ok: n => five.match({
Ok: o => ok(n + o),
Err: e => err(e),
}),
Err: e => err(e),
}); // Err('oh no')
And this kind of thing comes up quite often once you're using Result
to
handle errors throughout your application.
For another example, imagine you need to compare the equality of two
ImmutableJS data structures, where a ===
comparison won't work. With ap
,
that's as simple as this:
import { ok } from 'true-myth/result';
import { is as immutableIs, Set } from 'immutable';
const is = (first: unknown) => (second: unknown) =>
immutableIs(first, second);
const x = ok(Set.of(1, 2, 3));
const y = ok(Set.of(2, 3, 4));
ok(is).ap(x).ap(y); // Ok(false)
Without ap
, we're back to that gnarly nested match
:
import Result, { ok, err } from 'true-myth/result';
import { is, Set } from 'immutable';
const x = ok(Set.of(1, 2, 3));
const y = ok(Set.of(2, 3, 4));
x.match({
Ok: iX => y.match({
Ok: iY => Result.of(is(iX, iY)),
Err: (e) => ok(false),
})
Err: (e) => ok(false),
}); // Ok(false)
In summary: anywhere you have two Result
instances and need to perform an
operation that uses both of them, ap
is your friend.
Two things to note, both regarding currying:
ap
must be curried. That is, they must be of the
form (for add) (a: number) => (b: number) => a + b
, not the more usual
(a: number, b: number) => a + b
you see in JavaScript more generally.(Unfortunately, these do not currently work with lodash or Ramda's curry
helper functions. A future update to the type definitions may make that
work, but the intermediate types produced by those helpers and the more
general function types expected by this function do not currently align.)
ap
as many times as there are arguments to the
function you're dealing with. So in the case of this add3
function,
which has the "arity" (function argument count) of 3 (a
and b
), you'll
need to call ap
twice: once for a
, and once for b
. To see why, let's
look at what the result in each phase is:const add3 = (a: number) => (b: number) => (c: number) => a + b + c;
const resultAdd = ok(add); // Ok((a: number) => (b: number) => (c: number) => a + b + c)
const resultAdd1 = resultAdd.ap(ok(1)); // Ok((b: number) => (c: number) => 1 + b + c)
const resultAdd1And2 = resultAdd1.ap(ok(2)) // Ok((c: number) => 1 + 2 + c)
const final = maybeAdd1.ap(ok(3)); // Ok(4)
So for toString
, which just takes a single argument, you would only need
to call ap
once.
const toStr = (v: { toString(): string }) => v.toString();
ok(toStr).ap(12); // Ok("12")
One other scenario which doesn't come up quite as often but is conceivable
is where you have something that may or may not actually construct a function
for handling a specific Result
scenario. In that case, you can wrap the
possibly-present in ap
and then wrap the values to apply to the function to
in Result
themselves.
Because Result
often requires you to type out the full type parameterization
on a regular basis, it's convenient to use TypeScript's typeof
operator to
write out the type of a curried function. For example, if you had a function
that simply merged three strings, you might write it like this:
import Result from 'true-myth/result';
import { curry } from 'lodash';
const merge3Strs = (a: string, b: string, c: string) => string;
const curriedMerge = curry(merge3Strs);
const fn = Result.ok<typeof curriedMerge, string>(curriedMerge);
The alternative is writing out the full signature long-form:
const fn = Result.ok<(a: string) => (b: string) => (c: string) => string, string>(curriedMerge);
Aside:* ap
is not named apply
because of the overlap with JavaScript's
existing apply
function – and although strictly speaking, there isn't any
direct overlap (Result.apply
and Function.prototype.apply
don't intersect
at all) it's useful to have a different name to avoid implying that they're
the same.
result of a function from T to U
result of a T to apply to fn
Allows quick triple-equal equality check between the values inside two
Result
s without having to unwrap them first.
const a = Result.of(3)
const b = Result.of(3)
const c = Result.of(null)
const d = Result.nothing()
Result.equals(a, b) // true
Result.equals(a, c) // false
Result.equals(c, d) // true
Create an instance of Err
.
If you need to create an instance with a specific type (as you do whenever you are not constructing immediately for a function return or as an argument to a function), you can use a type parameter:
const notString = Result.err<number, string>('something went wrong');
Note: passing nothing, or passing null
or undefined
explicitly, will
produce a Result<T, Unit>
, rather than producing the nonsensical and in
practice quite annoying Result<null, string>
etc. See Unit
for
more.
const normalResult = Result.err<number, string>('oh no');
const explicitUnit = Result.err<number, Unit>(Unit);
const implicitUnit = Result.err<number, Unit>();
In the context of an immediate function return, or an arrow function with a single expression value, you do not have to specify the types, so this can be quite convenient.
type SomeData = {
//...
};
const isValid = (data: SomeData): boolean => {
// true or false...
}
const arrowValidate = (data: SomeData): Result<number, Unit> =>
isValid(data) ? Result.ok(42) : Result.err();
function fnValidate(data: someData): Result<number, Unit> {
return isValid(data) ? Result.ok(42) : Result.err();
}
The type of the item contained in the Result
.
Create an instance of Err
.
If you need to create an instance with a specific type (as you do whenever you are not constructing immediately for a function return or as an argument to a function), you can use a type parameter:
const notString = Result.err<number, string>('something went wrong');
Note: passing nothing, or passing null
or undefined
explicitly, will
produce a Result<T, Unit>
, rather than producing the nonsensical and in
practice quite annoying Result<null, string>
etc. See Unit
for
more.
const normalResult = Result.err<number, string>('oh no');
const explicitUnit = Result.err<number, Unit>(Unit);
const implicitUnit = Result.err<number, Unit>();
In the context of an immediate function return, or an arrow function with a single expression value, you do not have to specify the types, so this can be quite convenient.
type SomeData = {
//...
};
const isValid = (data: SomeData): boolean => {
// true or false...
}
const arrowValidate = (data: SomeData): Result<number, Unit> =>
isValid(data) ? Result.ok(42) : Result.err();
function fnValidate(data: someData): Result<number, Unit> {
return isValid(data) ? Result.ok(42) : Result.err();
}
The type of the item contained in the Result
.
Local implementation of {@linkcode Toolbelt.toOkOrErr toOkOrErr} from {@linkcode Toolbelt} for backwards compatibility. Prefer to import it from there instead:
import type { toOkOrErr } from 'true-myth/toolbelt';
Map over a Result
instance: apply the function to the wrapped
value if the instance is Ok
, and return the wrapped error value
wrapped as a new Err
of the correct type (Result<U, E>
) if the
instance is Err
.
map
works a lot like Array.prototype.map
, but with one important
difference. Both Result
and Array
are containers for other kinds of items,
but where Array.prototype.map
has 0 to n items, a Result
always has
exactly one item, which is either a success or an error instance.
Where Array.prototype.map
will apply the mapping function to every item in
the array (if there are any), Result.map
will only apply the mapping
function to the (single) element if an Ok
instance, if there is one.
If you have no items in an array of numbers named foo
and call foo.map(x => x + 1)
, you'll still some have an array with nothing in it. But if you have
any items in the array ([2, 3]
), and you call foo.map(x => x + 1)
on it,
you'll get a new array with each of those items inside the array "container"
transformed ([3, 4]
).
With this map
, the Err
variant is treated by the map
function kind of
the same way as the empty array case: it's just ignored, and you get back a
new Result
that is still just the same Err
instance. But if you have an
Ok
variant, the map function is applied to it, and you get back a new
Result
with the value transformed, and still wrapped in an Ok
.
import { ok, err, map, toString } from 'true-myth/result';
const double = n => n * 2;
const anOk = ok(12);
const mappedOk = map(double, anOk);
console.log(toString(mappedOk)); // Ok(24)
const anErr = err("nothing here!");
const mappedErr = map(double, anErr);
console.log(toString(mappedOk)); // Err(nothing here!)
The type of the value wrapped in an Ok
instance, and taken as
the argument to the mapFn
.
The type of the value wrapped in the new Ok
instance after
applying mapFn
, that is, the type returned by mapFn
.
The type of the value wrapped in an Err
instance.
The function to apply the value to if result
is Ok
.
The Result
instance to map over.
A new Result
with the result of applying mapFn
to the value
in an Ok
, or else the original Err
value wrapped in the new
instance.
Map over a Ok
, exactly as in map
, but operating on the
value wrapped in an Err
instead of the value wrapped in the
Ok
. This is handy for when you need to line up a bunch of
different types of errors, or if you need an error of one shape to be in a
different shape to use somewhere else in your codebase.
import { ok, err, mapErr, toString } from 'true-myth/result';
const reason = (err: { code: number, reason: string }) => err.reason;
const anOk = ok(12);
const mappedOk = mapErr(reason, anOk);
console.log(toString(mappedOk)); // Ok(12)
const anErr = err({ code: 101, reason: 'bad file' });
const mappedErr = mapErr(reason, anErr);
console.log(toString(mappedErr)); // Err(bad file)
The type of the value wrapped in the Ok
of the Result
.
The type of the value wrapped in the Err
of the Result
.
The type of the value wrapped in the Err
of a new Result
,
returned by the mapErrFn
.
The function to apply to the value wrapped in Err
if
result
is an Err
.
The Result
instance to map over an error case for.
Map over a Result
instance as in map
and get out the
value if result
is an Ok
, or return a default value if result
is an Err
.
import { ok, err, mapOr } from 'true-myth/result';
const length = (s: string) => s.length;
const anOkString = ok('a string');
const theStringLength = mapOr(0, anOkString);
console.log(theStringLength); // 8
const anErr = err('uh oh');
const anErrMapped = mapOr(0, anErr);
console.log(anErrMapped); // 0
The default value to use if result
is an Err
.
The function to apply the value to if result
is an Ok
.
The Result
instance to map over.
Map over a Result
instance as in map
and get out the
value if result
is Ok
, or apply a function (orElseFn
) to the
value wrapped in the Err
to get a default value.
Like mapOr
but using a function to transform the error into a
usable value instead of simply using a default value.
import { ok, err, mapOrElse } from 'true-myth/result';
const summarize = (s: string) => `The response was: '${s}'`;
const getReason = (err: { code: number, reason: string }) => err.reason;
const okResponse = ok("Things are grand here.");
const mappedOkAndUnwrapped = mapOrElse(getReason, summarize, okResponse);
console.log(mappedOkAndUnwrapped); // The response was: 'Things are grand here.'
const errResponse = err({ code: 500, reason: 'Nothing at this endpoint!' });
const mappedErrAndUnwrapped = mapOrElse(getReason, summarize, errResponse);
console.log(mappedErrAndUnwrapped); // Nothing at this endpoint!
The type of the wrapped Ok
value.
The type of the resulting value from applying mapFn
to the
Ok
value or orElseFn
to the Err
value.
The type of the wrapped Err
value.
The function to apply to the wrapped Err
value to get a
usable value if result
is an Err
.
The function to apply to the wrapped Ok
value if result
is
an Ok
.
The Result
instance to map over.
Performs the same basic functionality as unwrapOrElse
, but instead
of simply unwrapping the value if it is Ok
and applying a value to
generate the same default type if it is Err
, lets you supply
functions which may transform the wrapped type if it is Ok
or get a default
value for Err
.
This is kind of like a poor man's version of pattern matching, which JavaScript currently lacks.
Instead of code like this:
import Result, { isOk, match } from 'true-myth/result';
const logValue = (mightBeANumber: Result<number, string>) => {
console.log(
mightBeANumber.isOk
? mightBeANumber.value.toString()
: `There was an error: ${unsafelyGetErr(mightBeANumber)}`
);
};
...we can write code like this:
import Result, { match } from 'true-myth/result';
const logValue = (mightBeANumber: Result<number, string>) => {
const value = match(
{
Ok: n => n.toString(),
Err: e => `There was an error: ${e}`,
},
mightBeANumber
);
console.log(value);
};
This is slightly longer to write, but clearer: the more complex the resulting expression, the hairer it is to understand the ternary. Thus, this is especially convenient for times when there is a complex result, e.g. when rendering part of a React component inline in JSX/TSX.
A lightweight object defining what to do in the case of each variant.
Create an instance of Ok
.
If you need to create an instance with a specific type (as you do whenever you are not constructing immediately for a function return or as an argument to a function), you can use a type parameter:
const yayNumber = Result.ok<number, string>(12);
Note: passing nothing, or passing null
or undefined
explicitly, will
produce a Result<Unit, E>
, rather than producing the nonsensical and in
practice quite annoying Result<null, string>
etc. See Unit
for
more.
const normalResult = Result.ok<number, string>(42);
const explicitUnit = Result.ok<Unit, string>(Unit);
const implicitUnit = Result.ok<Unit, string>();
In the context of an immediate function return, or an arrow function with a single expression value, you do not have to specify the types, so this can be quite convenient.
type SomeData = {
//...
};
const isValid = (data: SomeData): boolean => {
// true or false...
}
const arrowValidate = (data: SomeData): Result<Unit, string> =>
isValid(data) ? Result.ok() : Result.err('something was wrong!');
function fnValidate(data: someData): Result<Unit, string> {
return isValid(data) ? Result.ok() : Result.err('something was wrong');
}
The type of the item contained in the Result
.
Create an instance of Ok
.
If you need to create an instance with a specific type (as you do whenever you are not constructing immediately for a function return or as an argument to a function), you can use a type parameter:
const yayNumber = Result.ok<number, string>(12);
Note: passing nothing, or passing null
or undefined
explicitly, will
produce a Result<Unit, E>
, rather than producing the nonsensical and in
practice quite annoying Result<null, string>
etc. See Unit
for
more.
const normalResult = Result.ok<number, string>(42);
const explicitUnit = Result.ok<Unit, string>(Unit);
const implicitUnit = Result.ok<Unit, string>();
In the context of an immediate function return, or an arrow function with a single expression value, you do not have to specify the types, so this can be quite convenient.
type SomeData = {
//...
};
const isValid = (data: SomeData): boolean => {
// true or false...
}
const arrowValidate = (data: SomeData): Result<Unit, string> =>
isValid(data) ? Result.ok() : Result.err('something was wrong!');
function fnValidate(data: someData): Result<Unit, string> {
return isValid(data) ? Result.ok() : Result.err('something was wrong');
}
The type of the item contained in the Result
.
The value to wrap in a Result.Ok
.
Provide a fallback for a given Result
. Behaves like a logical
or
: if the result
value is an Ok
, returns that result
;
otherwise, returns the defaultResult
value.
This is useful when you want to make sure that something which takes a
Result
always ends up getting an Ok
variant, by supplying a default value
for the case that you currently have an Err
.
import { ok, err, Result, or } from 'true-utils/result';
const okA = ok<string, string>('a');
const okB = ok<string, string>('b');
const anErr = err<string, string>(':wat:');
const anotherErr = err<string, string>(':headdesk:');
console.log(or(okB, okA).toString()); // Ok(A)
console.log(or(anErr, okA).toString()); // Ok(A)
console.log(or(okB, anErr).toString()); // Ok(B)
console.log(or(anotherErr, anErr).toString()); // Err(:headdesk:)
The type wrapped in the Ok
case of result
.
The type wrapped in the Err
case of result
.
The type wrapped in the Err
case of defaultResult
.
The Result
to use if result
is an Err
.
The Result
instance to check.
result
if it is an Ok
, otherwise defaultResult
.
Sometimes you need to perform an operation using other data in the environment
to construct the fallback value. In these situations, you can pass a function
(which may be a closure) as the elseFn
to generate the fallback Result<T>
.
It can then transform the data in the Err
to something usable as an
Ok
, or generate a new Err
instance as appropriate.
Useful for transforming failures to usable data.
The function to apply to the contents of the Err
if result
is an Err
, to create a new Result
.
The Result
to use if it is an Ok
.
The result
if it is Ok
, or the Result
returned by elseFn
if result
is an `Err.
Create an Object
representation of a Result
instance.
Useful for serialization. JSON.stringify()
uses it.
The value to convert to JSON
The JSON representation of the Result
Create a String
representation of a Result
instance.
An Ok
instance will be Ok(<representation of the value>)
, and an
Err
instance will be Err(<representation of the error>)
, where
the representation of the value or error is simply the value or error's own
toString
representation. For example:
call | output |
---|---|
toString(ok(42)) |
Ok(42) |
toString(ok([1, 2, 3])) |
Ok(1,2,3) |
toString(ok({ an: 'object' })) |
Ok([object Object]) n |
toString(err(42)) |
Err(42) |
toString(err([1, 2, 3])) |
Err(1,2,3) |
toString(err({ an: 'object' })) |
Err([object Object]) |
The type of the wrapped value; its own .toString
will be used
to print the interior contents of the Just
variant.
The value to convert to a string.
The string representation of the Maybe
.
Execute the provided callback, wrapping the return value in Ok
or
Err(error)
if there is an exception.
const aSuccessfulOperation = () => 2 + 2;
const anOkResult = Result.tryOr('Oh noes!!1', () => {
aSuccessfulOperation()
}); // => Ok(4)
const thisOperationThrows = () => throw new Error('Bummer');
const anErrResult = Result.tryOr('Oh noes!!1', () => {
thisOperationThrows();
}); // => Err('Oh noes!!1')
The error value in case of an exception
The callback to try executing
Execute the provided callback, wrapping the return value in Ok
.
If there is an exception, return a Err
of whatever the onError
function returns.
const aSuccessfulOperation = () => 2 + 2;
const anOkResult = Result.tryOrElse(
(e) => e,
aSuccessfulOperation
); // => Ok(4)
const thisOperationThrows = () => throw 'Bummer'
const anErrResult = Result.tryOrElse((e) => e, () => {
thisOperationThrows();
}); // => Err('Bummer')
A function that takes e
exception and returns what will
be wrapped in a Result.Err
The callback to try executing
This is the recommended way to get a value out of a Result
most of the time.
import { ok, err, unwrapOr } from 'true-myth/result';
const anOk = ok<number, string>(12);
console.log(unwrapOr(0, anOk)); // 12
const anErr = err<number, string>('nooooo');
console.log(unwrapOr(0, anErr)); // 0
The value wrapped in the Ok
.
The value wrapped in the Err
.
The value to use if result
is an Err
.
The Result
instance to unwrap if it is an Ok
.
The content of result
if it is an Ok
, otherwise
defaultValue
.
Safely get the value out of a Result
by returning the wrapped
value if it is Ok
, or by applying orElseFn
to the value in the
Err
.
This is useful when you need to generate a value (e.g. by using current
values in the environment – whether preloaded or by local closure) instead of
having a single default value available (as in unwrapOr
).
import { ok, err, unwrapOrElse } from 'true-myth/result';
// You can imagine that someOtherValue might be dynamic.
const someOtherValue = 2;
const handleErr = (errValue: string) => errValue.length + someOtherValue;
const anOk = ok<number, string>(42);
console.log(unwrapOrElse(handleErr, anOk)); // 42
const anErr = err<number, string>('oh teh noes');
console.log(unwrapOrElse(handleErr, anErr)); // 13
The value wrapped in the Ok
.
The value wrapped in the Err
.
A function applied to the value wrapped in result
if it is
an Err
, to generate the final value.
The result
to unwrap if it is an Ok
.
The value wrapped in result
if it is Ok
or the value
returned by orElseFn
applied to the value in Err
.
Generated using TypeDoc
Result
A
Result<T, E>
is a type representing the value result of an operation which may fail, with a successful value of typeT
or an error of typeE
.If the value is present, it is
Ok(value)
. If it's absent, it'sErr(reason)
. This provides a type-safe container for dealing with the possibility that an error occurred, without needing to scattertry
/catch
blocks throughout your codebase. This has two major advantages:Having the possibility of an error handed to you as part of the type of an item gives you the flexibility to do the same kinds of things with it that you might with any other nice container type. For example, you can use
map
to apply a transformation if the item represents a successful outcome, and even if the result was actually an error, it won't break under you.To make that concrete, let's look at an example. In normal JavaScript, you might have something like this:
If we wanted to handle that error, we'd need to first of all know that the function could throw an error. Assuming we knew that – probably we'd figure it out via painful discovery at runtime, or by documenting it in our JSDoc – then we'd need to wrap it up in a
try
/catch
block:This is a pain to work with!
The next thing we might try is returning an error code and mutating an object passed in. (This is the standard pattern for non-exception-based error handling in C, C++, Java, and C#, for example.) But that has a few problems:
You have to mutate an object. This doesn't work for simple items like numbers, and it can also be pretty unexpected behavior at times – you want to know when something is going to change, and mutating freely throughout a library or application makes that impossible.
You have to make sure to actually check the return code to make sure it's valid. In theory, we're all disciplined enough to always do that. In practice, we often end up reasoning, Well, this particular call can never fail... (but of course, it probably can, just not in a way we expect).
We don't have a good way to return a reason for the error. We end up needing to introduce another parameter, designed to be mutated, to make sure that's possible.
Even if you go to all the trouble of doing all of that, you need to make sure – every time – that you use only the error value if the return code specified an error, and only the success value if the return code specified that it succeeded.
(Note that in slightly varied form, this is also basically what the Node.js callback pattern gives you. It's just a conventional way of needing to check for an error on every callback invocation, since you don't actually have a return code in that case.)
Our way out is
Result
. It lets us just return one thing from a function, which encapsulates the possibility of failure in the very thing we return. We get:try
/catch
to worry about!)Here's what that same example from above would look like using
Result
:Note that if we tried to call
mightSucceed(true) * 2
here, we'd get a type error – this wouldn't make it past the compile step. Instead, we need to use one of the helper functions (or methods) to manipulate the value in a safe way.Using
Result
The library is designed to be used with a functional style, allowing you to compose operations easily. Thus, standalone pure function versions of every operation are supplied. However, the same operations also exist on the
Ok
andErr
types directly, so you may also write them in a more traditional "fluent" object style.Examples: functional style
Examples: fluent object invocation
You can also use a "fluent" method chaining style to apply the various helper functions to a
Result
instance:Writing type constraints
When creating a
Result
instance, you'll nearly always be using either theOk
or theErr
type, so the type system won't necessarily be able to infer the other type parameter.In TypeScript, because of the direction type inference happens, you will need to specify the type at declaration to make it type check when returning values from a function with a specified type. Note that this only applies when the instance is declared in its own statement and returned separately, not when it is the expression value of a single-expression arrow function or the explicit return value of any function.