Result Monad - monad represents some result in different states - Initial, Pending, Success and Failure.
Inspired by RemoteData type, but depends not on fp-ts but on smaller @sweet-monads
The problem it solving present is a very common one. You are loading a list of things but instead of showing a loading indicator you just see zero items. Same happens with actual data loading - sometimes you just do not think it will fail.
In my mental model, REST requests have one of four states:
- We haven't asked yet.
- We've asked, but we haven't got a response yet.
- We got a response, but it was an error.
- We got a response, and it was the data we wanted.
That is the purpose of this library - allow clean usage over this states.
Better explanation your can read in the artice How Elm Slays a UI Antipattern
npm install @lonli-lokli/ts-result
import { Result, success } from "@lonli-lokli/ts-result";
class UserNotFoundError extends Error {
name: "UserNotFoundError";
}
type User = { email: string; password: string };
function getUser(id: number): Result<UserNotFoundError, User> {
return success({ email: "test@gmail.com", password: "test" });
}
// Result<UserNotFoundError, string>
const user = getUser(1).map(({ email }) => email);
chain
merge
mergeInOne
mergeInMany
initial
pending
failure
success
from
fromPromise
fromTry
fromMaybe
fromEither
fromNullable
isResult
Result#isInitial
Result#isPending
Result#isFailure
Result#isSuccess
Result#or
Result#join
Result#map
Result#mapSuccess
Result#mapFailure
Result#asyncMap
Result#apply
Result#asyncApply
Result#chain
Result#asyncChain
Result#toEither
Result#toMaybe
Result#toNullable
Result#toUndefined
Result#unwrap
Result#fold
Helpers
function chain<F, S, NF, NS>(fn: (v: S) => Promise<Result<NF, NS>>): (m: Result<F, s>) => Promise<Result<F | NF, NS>>;
fn: (v: S) => Promise<Result<NF, NS>>
- function which should be applied asynchronously toResult<F, S>
value- Returns function with
Result<F, S>
argument and promisiedResult
with new error or mapped byfn
value (could be used insidePromise#then
function).
Example:
const getValue = async () => success(1);
// Result<TypeError, number>
const result = await getValue()
.then(chain(async v => success(v * 2)))
.then(chain(async g => failure(new TypeError("Unexpected"))));
Alias for mergeInOne
function merge<F1, S1>(values: [Result<F1, S1>]): Result<F1, [S1]>;
function merge<F1, S1, F2, S2>(values: [Result<F1, S1>, Result<F2, S2>]): Result<F1 | F2, [S1, S2]>;
function merge<F1, S1, F2, S2, F3, S3>(
values: [Result<F1, S1>, Result<F2, S2>, Result<F3, S3>]
): Result<F1 | F2 | F3, [S1, S2, S3]>;
// ... until 10 elements
values: Array<Result<F, S>>
- Array of Result values which will be merged into Result of Array- Returns
Result<F, Array<S>>
which will containSuccess<Array<S>>
if all of array elements wasSuccess<R>
,Failure<F>
if all of array elements wasFailure<F>
,Initial
if at least oneInitial
, otherwisePending
.
Example:
const v1 = initial; // Result<never, never>.Initial
const v2 = pending; // Result<never, never>.Pending
const v3 = success<TypeError, number>(2); // Result<TypeError, number>.Success
const v4 = success<ReferenceError, string>("test"); // Result<ReferenceError, string>.Success
const v5 = failure<Error, boolean>(new Error()); // Result<Error, boolean>.Failure
const r1 = merge([v1, v2]); // Result<never, [number, number]>.Initial
const r2 = merge([v2, v5]); // Result<Error, [never, boolean]>.Pending
const r3 = merge([v3, v4]); // Result<TypeError | ReferenceError, [number, string]>.Success
const r4 = merge([v3, v4, v5]); // Result<TypeError | Error | ReferenceError, [number, string, boolean]>.Failure
function merge<F1, S1>(values: [Result<F1, S1>]): Result<F1, [S1]>;
function merge<F1, S1, F2, S2>(values: [Result<F1, S1>, Result<F2, S2>]): Result<F1 | F2, [S1, S2]>;
function merge<F1, S1, F2, S2, F3, S3>(
values: [Result<F1, S1>, Result<F2, S2>, Result<F3, S3>]
): Result<F1 | F2 | F3, [S1, S2, S3]>;
// ... until 10 elements
values: Array<Result<F, S>>
- Array of Result values which will be merged into Result of Array- Returns
Result<F, Array<S>>
which will containSuccess<Array<S>>
if all of array elements wasSuccess<R>
,Failure<F>
if all of array elements wasFailure<F>
,Initial
if at least oneInitial
, otherwisePending
.
Example:
const v1 = initial; // Result<TypeError, number>.Initial
const v2 = pending; // Result<TypeError, number>.Pending
const v3 = success<TypeError, number>(2); // Result<TypeError, number>.Success
const v4 = success<ReferenceError, string>("test"); // Result<ReferenceError, string>.Success
const v5 = failure<Error, boolean>(new Error()); // Result<Error, boolean>.Failure
const r1 = merge([v1, v2]); // Result<TypeError, [number, number]>.Initial
const r2 = merge([v2, v5]); // Result<TypeError | Error, [number, boolean]>.Pending
const r3 = merge([v3, v4]); // Result<TypeError | ReferenceError, [number, string]>.Success
const r4 = merge([v3, v4, v5]); // Result<TypeError | ReferenceError | Error, [number, string, boolean]>.Failure
static mergeInMany<F1, S1>(values: [Result<F1, S1>]): Result<Array<F1>, [S1]>;
static mergeInMany<F1, S1, F2, S2>(values: [Result<F1, S1>, Result<F2, S2>]): Result<Array<F1 | F2>, [S1, S2]>;
static mergeInMany<F1, S1, F2, S2, F3, S3>(
values: [Result<F1, S1>, Result<F2, S2>, Result<F3, S3>]
): Result<Array<F1 | F2 | F3>, [S1, S2, S3]>;
// ... until 10 elements
values: Array<Result<F, S>>
- Array of Result values which will be merged into Result of Array- Returns
Result<Array<F>, Array<S>>
which will containSuccess<Array<S>>
if all of array elements wasSuccess<R>
otherwise array of all catchedFailure<F>
values.
Example:
const v1 = success<TypeError, number>(2); // Result<TypeError, number>.Success
const v2 = failure<ReferenceError, string>("test"); // Result<ReferenceError, string>.Success
const v3 = failure<Error, boolean>(new Error()); // Result<Error, boolean>.Failure
merge([v1, v2]); // Result<Array<TypeError | ReferenceError>, [number, string]>.Success
merge([v1, v2, v3]); // Result<Array<TypeError | ReferenceError | Error>, [number, string, boolean]>.Failure
const initial: Result<never, never>;
- Returns
Result
withInitial
state which does not contain value.
Example:
const v1 = initial; // Result<undefined, never>.Initial
const pending: Result<F, S>;
- Returns
Result
withPending
state which does not contain value.
Example:
const v1 = pending; // Result<never, never>.Initial
function failure<F, S>(value: F): Result<F, S>;
- Returns
Result
withFailure
state which contain value withF
type.
Example:
const v1 = failure(new Error()); // Result<Error, never>.Failure
const v2 = failure<Error, number>(new Error()); // Result<Error, number>.Failure
function success<F, S>(value: S): Result<F, S>;
- Returns
Result
withSuccess
state which contain value withS
type.
Example:
const v1 = success(2); // Result<never, number>.Success
const v2 = success<Error, number>(2); // Result<Error, number>.Success
Alias for success
function from<S>(value: S): Result<never, S>;
- Returns
Result
withSuccess
state which contain value withS
type.
Example:
from(2); // Result<never, number>.Success
Returns Success
with function result or Failure
if function execution throws an error.
function fromTry<L, R>(fn: () => R): Result<L, R>;
fromTry(() => 2); // Result<unknown, number>.Success
fromTry(() => {
throw new Error("test");
}); // Result<unknown, never>.Failure
Returns promise of Success
if the provided promise fulfilled or Failure
with the error value if the provided promise rejected.
function fromPromise<R>(promise: Promise<R>): Promise<Result<unknown, R>>;
fromPromise(Promise.resolve(2)); // Promise<Result<unknown, number>.Right>
fromPromise(Promise.reject(new Error("test"))); // Promise<Result<unknown, never>.Left>
function fromMaybe<never, S>(value: Maybe<S>): Result<never, S>;
- Creates
Result
fromMaybe
inInitial
orSuccess
state.
Example:
fromMaybe(just(2)); // Result<never, number>.Success
fromMaybe(none()); // Result<never, number>.Initial
function fromEither<F, S>(value: Either<F, S>): Result<F, S>;
- Creates
Result
fromEither
inFailure
orSuccess
state.
Example:
fromEither(right<string, number>(10)); // Result<string, number>.Success
function fromNullable<T>(value: T): Result<unknown, NonNullable<T>>;
- Creates
Result
withSuccess
state which contain value withT
type if value is not null or undefined andinitial
otherwise.
Example:
fromNullable(10); // Result<unknown, number>.Success
fromNullable(null as Nullable<number>); // Result<unknown, number>.Initial
function isResult<F, S>(value: unknown | Result<F, S>): value is Result<L, R>;
- Returns
boolean
if givenvalue
is instance of Result constructor.
Example:
const value: unknown = 2;
if (isResult(value)) {
// ... value is Result<unknown, unknown> at this block
}
function isInitial(): boolean;
- Returns
true
if state ofResult
isInitial
otherwisefalse
Example:
const v1 = success(2);
const v2 = failure(2);
const v3 = initial();
v1.isInitial(); // false
v2.isInitial(); // false
v3.isInitial(); // true
function isPending(): boolean;
- Returns
true
if state ofResult
isPending
otherwisefalse
Example:
const v1 = success(2);
const v2 = failure(2);
const v3 = pending();
v1.isPending(); // false
v2.isPending(); // false
v3.isPending(); // true
function isFailure(): boolean;
- Returns
true
if state ofResult
isFailure
otherwisefalse
Example:
const v1 = success(2);
const v2 = failure(2);
v1.isFailure(); // false
v2.isFailure(); // true
function isSuccess(): boolean;
- Returns
true
if state ofResult
isSuccess
otherwisefalse
Example:
const v1 = success(2);
const v2 = failure(2);
v1.isSuccess(); // true
v2.isSuccess(); // false
function or<F, S>(x: Result<F, S>): Result<F, S>;
- Returns
Result<F, S>
. If state ofthis
isSuccess
thenthis
will be returned otherwisex
argument will be returned
Example:
const v1 = success<string, number>(2);
const v2 = failure<string, number>("Error 1");
const v3 = failure<string, number>("Error 2");
const v4 = success<string, number>(3);
const v5 = initial();
v1.or(v2); // v1 will be returned
v2.or(v1); // v1 will be returned
v2.or(v3); // v3 will be returned
v1.or(v4); // v1 will be returned
v5.or(v1); // v1 will be returned
v2.or(v3).or(v1); // v1 will be returned
v2.or(v1).or(v3); // v1 will be returned
v1.or(v2).or(v3); // v1 will be returned
v2.or(v5).or(v3); // v3 will be returned
function join<L1, L2, R>(this: Result<L1, Result<L2, R>>): Result<L1 | L2, R>;
this: Result<F1, Result<F2, S>>
-Result
instance which contains otherResult
instance asSuccess
value.- Returns unwrapped
Result
- if currentResult
hasSuccess
state and innerResult
hasSuccess
state then returns innerResult
Success
, if innerResult
hasFailure
state then return innerResult
Failure
otherwise outerResult
Failure
.
Example:
const v1 = success(success(2));
const v2 = success(failure(new Error()));
const v3 = failure<TypeError, Result<Error, number>>(new TypeError());
v1.join(); // Result.Success with value 2
v2.join(); // Result.Failure with value new Error
v3.join(); // Result.Failure with value new TypeError
Alias for Result#mapSuccess
function map<F, S, NewS>(fn: (val: S) => NewS): Result<F, NewS>;
- Returns mapped by
fn
function value wrapped byResult
ifResult
isSuccess
otherwiseResult
with current value
Example:
const v1 = success<Error, number>(2);
const v2 = failure<Error, number>(new Error());
const newVal1 = v1.map(a => a.toString()); // Result<Error, string>.Success with value "2"
const newVal2 = v2.map(a => a.toString()); // Result<Error, string>.Failure with value new Error()
function mapSuccess<F, S, NewS>(fn: (val: S) => NewS): Result<F, NewS>;
- Returns mapped by
fn
function value wrapped byResult
ifResult
isSuccess
otherwiseResult
with current value
Example:
const v1 = success<Error, number>(2);
const v2 = failure<Error, number>(new Error());
const newVal1 = v1.mapSuccess(a => a.toString()); // Result<Error, string>.Success with value "2"
const newVal2 = v2.mapSuccess(a => a.toString()); // Result<Error, string>.Failure with value new Error()
function mapFailure<F, S, NewF>(fn: (val: F) => NewF): Result<NewF, S>;
- Returns mapped by
fn
function value wrapped byResult
ifResult
isFailure
otherwiseResult
with current value
Example:
const v1 = success<Error, number>(2);
const v2 = failure<Error, number>(new Error());
const newVal1 = v1.mapFailure(a => a.toString()); // Result<string, number>.Right with value 2
const newVal2 = v2.mapFailure(a => a.toString()); // Result<string, number>.Left with value "Error"
function asyncMap<F, S, NewS>(fn: (val: S) => Promise<NewS>): Promise<Result<F, NewS>>;
- Returns
Promise
with mapped byfn
function value wrapped byResult
ifResult
isSuccess
otherwiseResult
with current value
Example:
const v1 = success<Error, number>(2);
const v2 = failure<Error, number>(new Error());
// Promise<Result<Error, string>.Success> with value "2"
const newVal1 = v1.asyncMap(a => Promise.resolve(a.toString()));
// Promise<Result<Error, string>.Failure> with value new Error()
const newVal2 = v2.asyncMap(a => Promise.resolve(a.toString()));
function apply<A, B>(this: Result<L, (a: A) => B>, arg: Result<L, A>): Result<L, B>;
function apply<A, B>(this: Result<L, A>, fn: Result<L, (a: A) => B>): Result<L, B>;
this | fn
- function wrapped by Result, which should be applied to valuearg
arg | this
- value which should be applied tofn
- Returns mapped by
fn
function value wrapped byResult
ifResult
isSuccess
otherwiseResult
with current value
Example:
const v1 = success<Error, number>(2);
const v2 = failure<Error, number>(new Error());
const fn1 = success<Error, (a: number) => number>((a: number) => a * 2);
const fn2 = failure<Error, (a: number) => number>(new Error());
const newVal1 = fn1.apply(v1); // Result<Error, number>.Right with value 4
const newVal2 = fn1.apply(v2); // Result<Error, number>.Left with value new Error()
const newVal3 = fn2.apply(v1); // Result<Error, number>.Left with value new Error()
const newVal4 = fn2.apply(v2); // Result<Error, number>.Left with value new Error()
Async variant of Result#apply
asyncApply<A, B>(
this: Result<F, (a: Promise<A> | A) => Promise<B>>,
arg: Result<F, Promise<A>>): Promise<Result<F, B>>;
asyncApply<A, B>(
this: Result<F, Promise<A>>,
fn: Result<F, Promise<(a: Promise<A> | A) => B>>): Promise<Result<F, B>>;
asyncApply<A, B>(
this: Result<F, Promise<A>> | Result<F, (a: Promise<A> | A) => Promise<B>>,
argOrFn: Result<F, Promise<A>> | Result<F, (a: Promise<A> | A) => Promise<B>>): Promise<Result<F, B>>
this | fn
- function wrapped by Result, which should be applied to valuearg
arg | this
- value which should be applied tofn
- Returns
Promise
with mapped byfn
function value wrapped byResult
ifResult
isSuccess
otherwiseResult
with current value
Example:
const v1 = success<Error, number>(2);
const v2 = failure<Error, number>(new Error());
const fn1 = success<Error, (a: number) => Promise<number>>((a: number) => Promise.resolve(a * 2));
const fn2 = failure<Error, (a: number) => Promise<number>>(new Error());
const newVal1 = fn1.asyncApply(v1); // Promise<Either<Error, number>.Right> with value 4
const newVal2 = fn1.asyncApply(v2); // Promise<Either<Error, number>.Left> with value new Error()
const newVal3 = fn2.asyncApply(v1); // Promise<Either<Error, number>.Left> with value new Error()
const newVal4 = fn2.asyncApply(v2); // Promise<Either<Error, number>.Left> with value new Error()
function chain<F, S, NewF, NewS>(fn: (val: S) => Either<NewF, NewS>): Either<F | NewF, NewS>;
- Returns mapped by
fn
function value wrapped byResult
ifResult
isSuccess
and returned byfn
value isSuccess
too otherwiseResult
in other state,Initial
pwnsPending
andFailure
.
Example:
const v1 = success<Error, number>(2);
const v2 = failure<Error, number>(new Error());
const v3 = initial;
// Result<Error | TypeError, string>.Success with value "2"
const newVal1 = v1.chain(a => success<TypeError, string>(a.toString()));
// Result<Error | TypeError, string>.Failure with value new TypeError()
const newVal2 = v1.chain(a => failure<TypeError, string>(new TypeError()));
// Result<Error | TypeError, string>.Failure with value new Error()
const newVal3 = v2.chain(a => success<TypeError, string>(a.toString()));
// Result<Error | TypeError, string>.Failure with value new Error()
const newVal4 = v2.chain(a => failure<TypeError, string>(new TypeError()));
// Result<TypeError, string>.Initial with no value
const newVal5 = v3.chain(a => failure<TypeError, string>(new TypeError()));
function asyncChain<F, S, NewF, NewS>(fn: (val: S) => Promise<Result<NewF, NewS>>): Promise<Result<F | NewF, NewS>>;
- Returns
Promise
with mapped byfn
function value wrapped byResult
ifResult
isSuccess
and returned byfn
value isSuccess
too otherwiseResult
in other state,Initial
pwnsPending
andFailure
.
Example:
const v1 = success<Error, number>(2);
const v2 = failure<Error, number>(new Error());
const v3 = initial;
// Promise<Result<Error | TypeError, string>.Success> with value "2"
const newVal1 = v1.asyncChain(a => Promise.resolve(right<TypeError, string>(a.toString())));
// Promise<Result<Error | TypeError, string>.Failure> with value new TypeError()
const newVal2 = v1.asyncChain(a => Promise.resolve(left<TypeError, string>(new TypeError()));
// Promise<Result<Error | TypeError, string>.Failure> with value new Error()
const newVal3 = v2.asyncChain(a => Promise.resolve(right<TypeError, string>(a.toString())));
// Promise<Result<Error | TypeError, string>.Failure> with value new Error()
const newVal4 = v2.asyncChain(a => Promise.resolve(left<TypeError, string>(new TypeError())));
// Promise<Result<Error | TypeError, string>.Initial> with no value
const newVal5 = v3.asyncChain(a => Promise.resolve(failure<TypeError, string>(new TypeError())));
function toEither<F, S>(onInitial: () => F, onPending: () => F): Either<F, S>;
- Converts
Result
intoEither
inLeft
orSuccess
state with fallbacks forInitial
andPending
states.
Example:
success<string, number>(10).toEither(
() => "initial state",
() => "pending state"
); // Either<string, number>.Right
function toMaybe<S>(): Maybe<S>;
- Converts
Result
intoMaybe
inJust
state ifResult
is inSuccess
state or toMaybe
inNone
state otherwise.
Example:
success<string, number>(10).toMaybe(); // Maybe<number>.Just
function toNullable<S>(): S | null;
- Returns S if
Result
is inSuccess
state and null otherwise
Example:
success<string, number>(10).toNullable(); // number | null
function toUndefined<S>(): S | undefined;
- Returns S if
Result
is inSuccess
state and undefined otherwise
Example:
success<string, number>(10).toUndefined(); // number | undefined
function unwrap<S>(): S;
- Returns S if
Result
is inSuccess
state and throws otherwise via provided factory or pure Error
Example:
success<string, number>(10).unwrap(); // number
initial.unwrap(); // throws default (Error)
pending.unwrap({ failure: () => new Error('Custom')}); // throws custom (Error)
function fold<D>(onInitial: () => D, onPending: () => D, onFailure: (failure: F) => D, onSuccess: (success: S) => D): S;
- Extracts value from
Result
and converts it toD
based on the factory
Example:
const onInitial = () => "it's initial"
const onPending = () => "it's pending"
const onFailure = (err) => "it's failure"
const onSuccess = (data) => `${data + 1}`
const f = fold(onInitial, onPending, onFailure, onSuccess)
f(initial()) // "it's initial"
f(pending()) // "it's pending"
f(failure(new Error('error text'))) // "it's failure"
f(success(21)) // '22'
// Value from Result instance
const { value } = success<Error, number>(2); // number | Error | undefined
const { value } = success(2); // number | undefined
const { value } = failure<Error, number>(new Error()); // number | Error | undefined
const { value } = failure(new Error()); // Error | undefined
success(2).unwrap() // number
failure(new TypeError()).unwrap() // throws
failure(2).unwrap() // throws (don't do this)
failure(2).unwrapOr(3) // returns 3
success(2).unwrapOr(3) // returns 2
failure(2).unwrapOrElse(num => num * 2) // returns 4
success(2).unwrapOrElse(num => num * 2) // returns 2
MIT (c)