Result<Ok, Error>
The Result
can replace exception flows.
Exceptions can be tricky to handle: there's nothing in the type system that tracks if an error has been handled, which is error prone, and adds to your mental overhead. Result
helps as it makes the value hold the success state, making it dead-simple to track with a type-system.
Just like the Option
type, the Result
type is a box that can have two states:
Ok(value)
Error(error)
Create a Result value
To create a result, use the Ok
and Error
constructors:
import { Result } from "@swan-io/boxed";
const ok = Result.Ok(1);
const notOk = Result.Error("something happened");
You can convert an option to a Result
:
import { Result, Option } from "@swan-io/boxed";
const a = Result.fromOption(Option.Some(1), "NotFound");
// Ok<1>
const b = Result.fromOption(Option.None(), "NotFound");
// Error<"NotFound">
You get interop with exceptions and promises:
// Let's say you have some function that throws an error
const init = (id: string) => {
if (id.length !== 24) {
throw new Error();
}
return new Client({ id });
};
const result = Result.fromExecution(() => init(id));
// Here, result will either be:
// - Ok(client)
// - Error(error)
// It works with promises too:
const value = await Result.fromPromise(() => fetch("/api"));
// `value` will either be:
// - Ok(res)
// - Error(error)
Result
values are referentially equal if they contain the same value, meaning that Result.Ok(1) === Result.Ok(1)
and Result.Error(1) === Result.Error(1)
.
Methods
The result type provides a few manipulation functions:
.map(f)
Result<A, E>.map<B>(f: (value: A) => B): Result<B, E>
If the result is Ok(value)
returns Ok(f(value))
, otherwise returns Error(error)
.
Result.Ok(2).map((x) => x * 2);
// Result.Ok<4>
Result.Ok(2).map((x) => Result.Ok(x * 2));
// Result.Ok<Result.Ok<4>>
.mapError(f)
Result<A, E>.mapError<F>(f: (value: E) => F): Result<A, F>
If the result is Error(error)
returns Error(f(error))
, otherwise returns Ok(value)
.
Result.Error(2).mapError((x) => x * 2);
// Result.Error<4>
Result.Error(2).mapError((x) => Result.Ok(x * 2));
// Result.Error<Result.Ok<4>>
.flatMap(f)
Result<A, E>.flatMap<B, F>(f: (value: A) => Result<B, F>): Result<B, F | E>
If the result is Ok(value)
returns f(value)
, otherwise returns Error(error)
.
Result.Ok(1).flatMap((x) =>
x > 2 ? Result.Error("some error") : Result.Ok(2),
);
// Result.Ok<2>
Result.Ok(3).flatMap((x) =>
x > 2 ? Result.Error("some error") : Result.Ok(2),
);
// Result.Error<"some error">
Result.Error("initial error").flatMap((x) =>
x > 2 ? Result.Error("some error") : Result.Ok(2),
);
// Result.Error<"initial error">
.flatMapError(f)
Result<A, E>.flatMapError<B, F>(f: (value: E) => Result<B, F>): Result<A | B, F>
If the result is Error(error)
returns f(error)
, otherwise returns Ok(value)
.
Result.Error(3).flatMapError((x) =>
x > 2 ? Result.Error("some error") : Result.Ok(2),
);
// Result.Error<"some error">
Result.Error(1).flatMapError((x) =>
x > 2 ? Result.Error("some error") : Result.Ok(2),
);
// Result.Ok<2>
Result.Ok("ok").flatMapError((x) =>
x > 2 ? Result.Error("some error") : Result.Ok(2),
);
// Result.Ok<"ok">
.getOr(defaultValue)
Alias:
getWithDefault
Result<A, E>.getOr(defaultValue: A): A
If the result is Ok(value)
returns value
, otherwise returns defaultValue
.
Result.Ok(2).getOr(1);
// 2
Result.Error(2).getOr(1);
// 1
.mapOr(defaultValue, mapper)
Result<A, E>.mapOr(defaultValue: B, mapper: (a: A) => B): B
If the option is Ok(value)
returns mapper(value)
, otherwise returns defaultValue
.
Result.Ok(2).mapOr(1, (x) => x * 2);
// 4
Result.Error("error").mapOr(1, (x) => x * 2);
// 1
.get()
Result<A, E>.get(): A
Returns the value contained in Ok(value)
. Only usable within a isOk()
check.
const value = result.get();
// does not compile
if (result.isOk()) {
const value = result.get();
// value
}
.getError()
Result<A, E>.getError(): E
Returns the error contained in Error(error)
. Only usable within a isError()
check.
const error = result.getError();
// does not compile
if (result.isError()) {
const error = result.getError();
// error
}
.isOk()
Result<A, E>.isOk(): boolean
Type guard. Checks if the result is Ok(value)
Result.Ok(2).isOk();
// true
Result.Error(2).isOk();
// false
if (result.isOk()) {
const value = result.get();
}
.isError()
Result<A, E>.isError(): boolean
Type guard. Checks if the result is Error(error)
Result.Ok(2).isError();
// false
Result.Error().isError();
// true
if (result.isError()) {
const value = result.getError();
}
.toOption()
Result<A, E>.toOption(): Option<A>
If the result is Ok(value)
returns Some(value)
, otherwise returns None
.
Result.Ok(2).toOption();
// Option.Some<2>
Result.Error(2).toOption();
// Option.None
.match()
Result<A, E>.match<B>(config: {
Ok: (value: A) => B;
Error: (error: E) => B;
}): B
Match the result state
const valueToDisplay = result.match({
Ok: (value) => value,
Error: (error) => {
console.error(error);
return "fallback";
},
});
.tap(func)
Result<A, E>.tap(func: (result: Result<A, E>) => unknown): Result<A, E>
Executes func
with result
, and returns result
. Useful for logging and debugging.
result.tap(console.log).map((x) => x * 2);
.tapOk(func)
Result<A, E>.tapOk(func: (value: A) => unknown): Result<A, E>
Executes func
with ok
, and returns result
. Useful for logging and debugging. No-op if result
is an error.
result.tapOk(console.log).map((x) => x * 2);
.tapError(func)
Result<A, E>.tapError(func: (error: E) => unknown): Result<A, E>
Executes func
with error
, and returns result
. Useful for logging and debugging. No-op if result
is ok.
result.tapError(console.log).map((x) => x * 2);
Statics
Result.fromPredicate(value, predicate, error)
fromPredicate(value: A, f: (value: A) => boolean, errorIfFalse: E): Result<A, E>
Creates an option from a value and a predicate. Will return Ok(value)
if predicate returns true
, Error(errorIfFalse)
if false
Result.fromPredicate(
value,
(value) => value % 2 === 0,
new Error("Odd number"),
);
// Ok<number> if `number` is even, Error<Error> if odd
Result.isResult(value)
isResult(value: unknown): boolean
Type guard, checks if the provided value is a result.
Result.isResult(Result.Ok(1));
// true
Result.isResult([]);
// false
Result.all(results)
all(options: Array<Result<A, E>>): Result<Array<A>, E>
Turns an "array of results of value" into a "result of array of value".
Result.all([Result.Ok(1), Result.Ok(2), Result.Ok(3)]);
// Result.Ok<[1, 2, 3]>
Result.all([Result.Error("error"), Result.Ok(2), Result.Ok(3)]);
// Result.Error<"error">
Result.allFromDict(results)
allFromDict(options: Dict<Result<A, E>>): Result<Dict<A>, E>
Turns a "dict of results of value" into a "result of dict of value".
Result.allFromDict({ a: Result.Ok(1), b: Result.Ok(2), c: Result.Ok(3) });
// Result.Ok<{a: 1, b: 2, c: 3}>
Result.allFromDict({
a: Result.Error("error"),
b: Result.Ok(2),
c: Result.Ok(3),
});
// Result.Error<"error">
Result.fromExecution(() => value)
fromExecution<A, E>(func: () => A) => Result<A, E>
Takes a function returning Value
that can throw an Error
and returns a Result<Value, Error>
Result.fromExecution(() => 1);
// Result.Ok<1>
Result.fromExecution(() => {
throw "Something went wrong";
});
// Result.Error<"Something went wrong">
Result.fromPromise(promise)
fromPromise<A, E>(promise: Promise<A>) => Promise<Result<A, E>>
Takes a Promise<Value>
that can fail with Error
and returns a Promise<Result<Value, Error>>
await Result.fromPromise(Promise.resolve(1));
// Result.Ok<1>
await Result.fromPromise(Promise.reject(1));
// Result.Error<1>
Result.fromOption(option, valueIfNone)
fromOption<A, E>(option: Option<A>, valueWhenNone: E): Result<A, E>
Takes a function returning Value
that can throw an Error
and returns a Result<Value, Error>
const a = Result.fromOption(Option.Some(1), "NotFound");
// Result.Ok<1>
const b = Result.fromOption(Option.None(), "NotFound");
// Result.Error<"NotFound">
TS Pattern interop
import { match, P } from "ts-pattern";
import { Result } from "@swan-io/boxed";
match(myResult)
.with(Result.P.Ok(P.select()), (value) => console.log(value))
.with(Result.P.Error(P.select()), (error) => {
console.error(error);
return "fallback";
})
.exhaustive();
Cheatsheet
Method | Input | Function input | Function output | Returned value |
---|---|---|---|---|
map | Ok(x) | x | y | Ok(y) |
map | Error(e) | not provided | not executed | Error(e) |
mapError | Ok(x) | not provided | not executed | Ok(x) |
mapError | Error(e) | e | f | Error(f) |
flatMap | Ok(x) | x | Ok(y) | Ok(y) |
flatMap | Ok(x) | x | Error(f) | Error(f) |
flatMap | Error(e) | not provided | not executed | Error(e) |
flatMapError | Ok(x) | not provided | not executed | Ok(x) |
flatMapError | Error(e) | e | Ok(y) | Ok(y) |
flatMapError | Error(e) | e | Error(f) | Error(f) |