@lonli-lokli/ts-result
v2.6.0
Published
The Result monad represents some result in different states - Initial, Pending, Success and Failure.
Downloads
181
Readme
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
Explanation
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
Installation
npm install @lonli-lokli/ts-result
Usage
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: "[email protected]", password: "test" });
}
// Result<UserNotFoundError, string>
const user = getUser(1).map(({ email }) => email);
API
chain
merge
mergeWithConfig
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#unwrapOr
Result#unwrapOrElse
Result#fold
Result#filter
Result#filtermap
Result#tap
Result#tapFailure
Result#recover
Result#recoverWith
Result#zip
Result#zipWith
Result#biMap
Helpers
chain
function chain<F, S, NF, NS>(fn: (val: S) => Result<NF, NS>): Result<F | NF, NS>;
- Returns a new Result by applying
fn
to the Success value of this Result - State handling priority:
- If this Result is
Initial
, returnsInitial
- If the next Result (returned by
fn
) isInitial
, returnsInitial
- If this Result is
Pending
or the next Result isPending
, returnsPending
- If this Result is
Failure
, returnsFailure
with current value - If the next Result is
Failure
, returnsFailure
with next value - Otherwise returns the next Result
- If this Result is
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()));
The chain method is particularly useful when you need to sequence operations that might fail or be in different states. It handles all possible state combinations according to the priority rules above.
merge
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
mergeWithConfig
function mergeWithConfig<F, S>(values: Result<F, S>[], config: { priority: 'failure' | 'pending' }): Result<F, S[]>;
Merges an array of Results with configurable priority between states.
Default behavior (priority: 'pending'):
- Returns Initial if ANY Result is Initial
- Returns Pending if ANY Result is Pending (and none are Initial)
- Returns Failure if ANY Result is Failure (and none are Initial/Pending)
- Returns Success only if ALL Results are Success
Failure priority (priority: 'failure'):
- Returns Failure if ANY Result is Failure
- Returns Initial if ANY Result is Initial
- Returns Pending if ANY Result is Pending
- Returns Success only if ALL Results are Success
const v1 = success<string, number>(1);
const v2 = failure('error');
const v3 = pending as Result<string, number>;
// Default behavior - Pending takes precedence
mergeWithConfig([v1, v2, v3], { priority: 'pending' }); // Result.Pending
// Failure priority
mergeWithConfig([v1, v2, v3], { priority: 'failure' }); // Result.Failure('error')
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<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
mergeInMany
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
initial
const initial: Result<never, never>;
- Returns
Result
withInitial
state which does not contain value.
Example:
const v1 = initial; // Result<undefined, never>.Initial
pending
const pending: Result<F, S>;
- Returns
Result
withPending
state which does not contain value.
Example:
const v1 = pending; // Result<never, never>.Initial
failure
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
success
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
from
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
fromTry
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
fromPromise
Returns promise of Success
if the provided promise fulfilled or Failure
with the error value if the provided promise rejected.
function fromPromise<F, S>(promise: Promise<S>): Promise<Result<F, S>>;
fromPromise(Promise.resolve(2)); // Promise<Result<unknown, number>.Right>
fromPromise(Promise.reject(new Error("test"))); // Promise<Result<unknown, never>.Left>
fromMaybe
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
fromEither
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
fromNullable
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
isResult
function isResult<F, S>(value: unknown | Result<F, S>): value is Result<F, S>;
- 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
}
Result#isInitial
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
Result#isPending
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
Result#isFailure
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
Result#isSuccess
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
Result#or
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
Result#join
function join<F1, F2, S>(this: Result<F1, Result<F2, S>>): Result<F1 | F2, S>;
this: Result<F1, Result<F2, R>>
-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
Result#map
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()
Result#mapSuccess
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()
Result#mapLeft
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"
Result#asyncMap
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()));
Result#apply
function apply<F, S, B>(this: Result<F, (a: S) => B>, arg: Result<F, S>): Result<F, B>;
function apply<F, S, B>(this: Result<F, S>, fn: Result<F, (a: S) => B>): Result<F, 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()
Result#asyncApply
Async variant of Result#apply
asyncApply<F, S, B>(
this: Result<F, (a: Promise<S> | S) => Promise<B>>,
arg: Result<F, Promise<S>>): Promise<Result<F, B>>;
asyncApply<F, S, B>(
this: Result<F, Promise<S>>,
fn: Result<F, Promise<(a: Promise<S> | S) => 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()
Result#chain
function chain<F, S, NF, NS>(fn: (val: S) => Result<NF, NS>): Result<F | NF, NS>;
- Returns a new Result by applying
fn
to the Success value of this Result - State handling priority:
- If this Result is
Initial
, returnsInitial
- If the next Result (returned by
fn
) isInitial
, returnsInitial
- If this Result is
Pending
or the next Result isPending
, returnsPending
- If this Result is
Failure
, returnsFailure
with current value - If the next Result is
Failure
, returnsFailure
with next value - Otherwise returns the next Result
- If this Result is
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()));
The chain method is particularly useful when you need to sequence operations that might fail or be in different states. It handles all possible state combinations according to the priority rules above.
Result#asyncChain
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())));
Result#toEither
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
Result#toMaybe
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
Result#toNullable
function toNullable<S>(): S | null;
- Returns S if
Result
is inSuccess
state and null otherwise
Example:
success<string, number>(10).toNullable(); // number | null
Result#toUndefined
function toUndefined<S>(): S | undefined;
- Returns S if
Result
is inSuccess
state and undefined otherwise
Example:
success<string, number>(10).toUndefined(); // number | undefined
Result#unwrap
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)
Result#unwrapOr
function unwrapOr<S>(s: S): S;
- Returns the success value if Result is Success, otherwise returns the provided default value.
Example:
const v1 = success<Error, number>(2);
const v2 = failure<Error, number>(new Error());
v1.unwrapOr(3); // returns 2
v2.unwrapOr(3); // returns 3
Result#unwrapOrElse
function unwrapOrElse(f: (l: F) => S): S;
- Returns the success value if Result is Success, otherwise returns the result of calling the provided function with the failure value.
Example:
const v1 = success<number, number>(2);
const v2 = failure<number, number>(3);
v1.unwrapOrElse(x => x * 2); // returns 2
v2.unwrapOrElse(x => x *2); // returns 6
Result#fold
function fold<D>(onInitial: () => D, onPending: () => D, onFailure: (failure: F) => D, onSuccess: (success: S) => D): D;
- Transforms the Result value into type D by providing handlers for all possible states
- Parameters:
onInitial: () => D
- Handler for Initial stateonPending: () => D
- Handler for Pending stateonFailure: (failure: F) => D
- Handler for Failure state, receives the failure valueonSuccess: (success: S) => D
- Handler for Success state, receives the success value
- Returns the result of calling the appropriate handler based on the Result state
Example:
const onInitial = () => "it's initial"
const onPending = () => "it's pending"
const onFailure = (err) => "it's failure"
const onSuccess = (data) => `${data + 1}`
const v1 = initial;
const v2 = pending;
const v3 = failure<string, number>('error');
const v4 = success<string, number>(21);
v1.fold(onInitial, onPending, onFailure, onSuccess) // "it's initial"
v2.fold(onInitial, onPending, onFailure, onSuccess) // "it's pending"
v3.fold(onInitial, onPending, onFailure, onSuccess) // "it's failure"
v4.fold(onInitial, onPending, onFailure, onSuccess) // "22"
The fold method is particularly useful when you need to handle all possible states of a Result and transform them into a single type. This pattern is common when you need to:
- Display different UI states
- Convert Result states into a common format
- Handle all possible outcomes in a type-safe way
Result#filter
function filter(predicate: (value: S) => boolean): Result<F | S, S>;
Validates a Success value using a predicate. If the predicate returns false, converts the Success to a Failure using the success value.
// Age validation
const age = success<string, number>(15);
const isAdult = age.filter(age => age >= 18);
// Result<string | number, number>.Failure with value 15
// Chaining validations
const validAge = success<string, number>(25)
.filter(age => age >= 0) // minimum age
.filter(age => age <= 120); // maximum age
// Result<string | number, number>.Success with value 25
Result#filterMap
function filterMap<NS>(f: (value: S) => Result<S, NS>): Result<F | S, NS>;
Combines filtering and mapping in one operation. Useful for transformations that might fail.
const parseIfPositive = (n: number) =>
n > 0 ? success(n.toString()) : failure(n);
success(5).filterMap(parseIfPositive)
// Result<number, string>.Success with value "5"
success(-1).filterMap(parseIfPositive)
// Result<number, string>.Failure with value -1
failure('error').filter(x => true) // stays Failure
initial.filter(x => true) // stays Initial
pending.filter(x => true) // stays Pending
failure('error').filterMap(fn) // stays Failure
initial.filterMap(fn) // stays Initial
pending.filterMap(fn) // stays Pending
Result#tap
function tap(f: (value: S) => void): Result<F, S>;
Executes a side effect function if the Result is Success, then returns the original Result unchanged. Useful for logging, debugging, or other side effects without modifying the Result chain.
success(5)
.tap(x => console.log('Value:', x)) // logs "Value: 5"
.map(x => x * 2); // Result<never, number>.Success(10)
failure('error')
.tap(x => console.log('Value:', x)) // nothing logged
.map(x => x * 2); // Result<string, number>.Failure('error')
Result#tapFailure
function tapFailure(f: (value: F) => void): Result<F, S>;
Executes a side effect function if the Result is Failure, then returns the original Result unchanged. Useful for error logging or debugging without modifying the Result chain.
success<Error, number>(5)
.tapFailure(e => console.error(e)) // nothing logged
.map(x => x * 2); // Result<Error, number>.Success(10)
failure(new Error('oops'))
.tapFailure(e => console.error(e)) // logs Error: oops
.map(x => x * 2); // Result<Error, number>.Failure(Error: oops)
Result#recover
function recover(value: S): Result<F, S>;
Recovers from a Failure state by providing a default success value.
const v1 = failure<string, number>('error');
const v2 = success<string, number>(5);
v1.recover(42); // Result<string, number>.Success with value 42
v2.recover(42); // Result<string, number>.Success with value 5 (unchanged)
Result#recoverWith
function recoverWith<NF, NS>(f: (error: F) => Result<NF, NS>): Result<NF, NS | S>;
Recovers from a Failure state by applying a function that returns a new Result. Useful for handling specific error cases differently or transforming errors.
const handler = (error: string): Result<Error, number> =>
error === 'known' ? success(42) : failure(new Error('still failed'));
failure('known').recoverWith(handler); // Result<Error, number>.Success(42)
failure('unknown').recoverWith(handler); // Result<Error, number>.Failure(Error: still failed)
success<string, number>(5).recoverWith(handler); // Result<Error, number>.Success(5)
// Initial and Pending states pass through
initial.recoverWith(handler); // Result.Initial
pending.recoverWith(handler); // Result.Pending
Result#zip
function zip<F2, S2>(other: Result<F2, S2>): Result<F | F2, [S, S2]>;
Combines two Results into a Result containing a tuple of their success values. Returns Failure if either Result is a Failure.
const num = success<string, number>(2);
const str = success<Error, string>('test');
num.zip(str) // Result<string | Error, [number, string]>.Success([2, 'test'])
num.zip(failure('error')) // Result<string | Error, [number, string]>.Failure('error')
Result#zipWith
function zipWith<F2, S2, R>(
other: Result<F2, S2>,
f: (a: S, b: S2) => R
): Result<F | F2, R>;
Combines two Results using a function. Returns Failure if either Result is a Failure.
const num1 = success<string, number>(2);
const num2 = success<Error, number>(3);
num1.zipWith(num2, (a, b) => a + b) // Result<string | Error, number>.Success(5)
num1.zipWith(failure('error'), (a, b) => a + b) // Result<string | Error, number>.Failure('error')
Result#bimap
function bimap<NF, NS>(
failureMap: (f: F) => NF,
successMap: (s: S) => NS
): Result<NF, NS>;
Maps both the Failure and Success values of a Result simultaneously. Useful for transforming both possible outcomes in one operation.
const result = success<string, number>(42);
// Transform both success and failure values
const transformed = result.bimap(
(error: string) => new Error(error), // transform failure
(value: number) => value.toString() // transform success
);
// Result<Error, string>.Success('42')
const failed = failure<string, number>('oops');
const transformedFailure = failed.bimap(
(error: string) => new Error(error),
(value: number) => value.toString()
);
// Result<Error, string>.Failure(Error: oops)
// Initial and Pending states pass through unchanged
initial.bimap(f, s) // Result.Initial
pending.bimap(f, s) // Result.Pending
Helpers
// 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
Development
License
MIT (c)