UNPKG

@jumpaku/async-result

Version:

A typescript library that provides Result, Option, and AsyncResult.

306 lines (299 loc) 9.72 kB
import { BaseError } from "make-error-cause"; import { AsyncResult } from "./AsyncResult"; export class ResultError<E> extends BaseError { readonly name: string = "ResultError"; readonly detail: E; constructor(error: E) { super( error instanceof Error ? error.message : "", error instanceof Error ? error : undefined ); this.detail = error; } } interface ResultTry { <V>(tryFun: () => V): Result<V, unknown>; <V, E>(tryFun: () => V, catchFun: (error: unknown) => E): Result<V, E>; } export const Result: { success: <V>(value: V) => Result<V, never>; failure: <E>(error: E) => Result<never, E>; try: ResultTry; isResult: <V, E>(obj: unknown) => obj is Result<V, E>; } = { success<V>(value: V): Result<V, never> { return new Success<V, never>(value); }, failure<E>(error: E): Result<never, E> { return new Failure<never, E>(error); }, try: <V, E>( tryFun: () => V, catchFun?: (error: unknown) => E ): Result<V, unknown> | Result<V, E> => { try { return Result.success(tryFun()); } catch (error: unknown) { return catchFun == null ? Result.failure(error) : Result.failure(catchFun(error)); } }, isResult<V, E>(obj: unknown): obj is Result<V, E> { return obj instanceof AbstractResult; }, }; abstract class AbstractResult<V, E> { private assertsThisIsResult(): asserts this is Result<V, E> { if (!this.isSuccess() && !this.isFailure()) throw new Error(); } abstract isSuccess(): this is Success<V, E>; abstract isFailure(): this is Failure<V, E>; match<X, Y>(onSuccess: (value: V) => X, onFailure: (error: E) => Y): X | Y { this.assertsThisIsResult(); return this.isSuccess() ? onSuccess(this.value) : onFailure(this.error); } orDefault(value: V): V { return this.isSuccess() ? this.value : value; } orRecover(f: (error: E) => V): V { this.assertsThisIsResult(); return this.isSuccess() ? this.value : f(this.error); } orThrow<F>(f?: (e: E) => F): V { this.assertsThisIsResult(); if (this.isFailure()) throw f != null ? f(this.error) : new ResultError(this.error); return this.value; } orNull(): V | null { return this.isSuccess() ? this.value : null; } orUndefined(): V | undefined { return this.isSuccess() ? this.value : undefined; } onSuccess(f: (value: V) => void): Result<V, E> { if (this.isSuccess()) f(this.value); this.assertsThisIsResult(); return this; } onFailure(f: (error: E) => void): Result<V, E> { this.assertsThisIsResult(); if (this.isFailure()) f(this.error); return this; } and<U, F>(other: Result<U, F>): Result<V | U, E | F> { return this.isFailure() ? this : other; } or<U, F>(other: Result<U, F>): Result<V | U, E | F> { return this.isSuccess() ? this : other; } map<U>(neverThrowFun: (value: V) => U): Result<U, E> { this.assertsThisIsResult(); return this.isSuccess() ? Result.success(neverThrowFun(this.value)) : this.castValue(); } mapAsync<U>(tryFun: (value: V) => U | Promise<U>): AsyncResult<U, unknown>; mapAsync<U, F>( tryFun: (value: V) => U | Promise<U>, catchFun: (error: unknown) => F ): AsyncResult<U, F>; mapAsync<U, F>( tryFun: (value: V) => U | Promise<U>, catchFun?: (error: unknown) => F ): AsyncResult<U, F | unknown> { return catchFun == null ? this.match( (v) => AsyncResult.try(() => tryFun(v)), (e) => AsyncResult.failure(e) ) : this.match( (v) => AsyncResult.try(() => tryFun(v), catchFun), (e) => AsyncResult.failure(e) ); } tryMap<U>(tryFun: (value: V) => U): Result<U, unknown>; tryMap<U, F>( tryFun: (value: V) => U, catchFun: (error: unknown) => F ): Result<U, E | F>; tryMap<U, F>( tryFun: (value: V) => U, catchFun?: (error: unknown) => F ): Result<U, E | F | unknown> { return (catchFun == null ? Result.try(() => this.map(tryFun)) : Result.try(() => this.map(tryFun), catchFun) ).flatMap((it) => it); } flatMap<U, F>(neverThrowFun: (value: V) => Result<U, F>): Result<U, E | F> { this.assertsThisIsResult(); return this.isSuccess() ? neverThrowFun(this.value) : this.castValue(); } flatMapAsync<U, F>( tryFun: (value: V) => Result<U, F> | AsyncResult<U, F> ): AsyncResult<U, unknown>; flatMapAsync<U, F, G>( tryFun: (value: V) => Result<U, F> | AsyncResult<U, F>, catchFun: (error: unknown) => G ): AsyncResult<U, E | F | G>; flatMapAsync<U, F, G>( tryFun: (value: V) => Result<U, F> | AsyncResult<U, F>, catchFun?: (error: unknown) => G ): AsyncResult<U, E | F | G | unknown> { this.assertsThisIsResult(); if (this.isFailure()) return AsyncResult.of(this.castValue()); try { const result = tryFun(this.value); return Result.isResult(result) ? AsyncResult.of(result) : result; } catch (error: unknown) { return catchFun == null ? AsyncResult.failure(error) : AsyncResult.failure(catchFun(error)); } } tryFlatMap<U, F>(tryFun: (value: V) => Result<U, F>): Result<U, unknown>; tryFlatMap<U, F, G>( tryFun: (value: V) => Result<U, F>, catchFun: (error: unknown) => G ): Result<U, E | F | G>; tryFlatMap<U, F, G>( tryFun: (value: V) => Result<U, F>, catchFun?: (error: unknown) => G ): Result<U, E | F | G | unknown> { return (catchFun == null ? Result.try(() => this.flatMap(tryFun)) : Result.try(() => this.flatMap(tryFun), catchFun) ).flatMap((it) => it); } recover(neverThrowFun: (error: E) => V): Result<V, never> { this.assertsThisIsResult(); return this.isFailure() ? Result.success(neverThrowFun(this.error)) : this.castError(); } recoverAsync<F>( tryFun: (error: E) => V | Promise<V> ): AsyncResult<V, unknown>; recoverAsync<F>( tryFun: (error: E) => V | Promise<V>, catchFun: (error: unknown) => F ): AsyncResult<V, F>; recoverAsync<F>( tryFun: (error: E) => V | Promise<V>, catchFun?: (error: unknown) => F ): AsyncResult<V, F | unknown> { this.assertsThisIsResult(); const f = async () => { this.assertsThisIsResult(); return this.isSuccess() ? this.value : tryFun(this.error); }; return catchFun == null ? AsyncResult.try(f) : AsyncResult.try(f, catchFun); } tryRecover(tryFun: (error: E) => V): Result<V, unknown>; tryRecover<F>( tryFun: (error: E) => V, catchFun: (error: unknown) => F ): Result<V, F>; tryRecover<F>( tryFun: (error: E) => V, catchFun?: (error: unknown) => F ): Result<V, F | unknown> { return (catchFun == null ? Result.try(() => this.recover(tryFun)) : Result.try(() => this.recover(tryFun), catchFun) ).flatMap((it) => it); } flatRecover<F>(neverThrowFun: (error: E) => Result<V, F>): Result<V, F> { this.assertsThisIsResult(); return this.isFailure() ? neverThrowFun(this.error) : this.castError(); } flatRecoverAsync<F>( tryFun: (error: E) => Result<V, F> | AsyncResult<V, F> ): AsyncResult<V, unknown>; flatRecoverAsync<F, G>( tryFun: (error: E) => Result<V, F> | AsyncResult<V, F>, catchFun: (error: unknown) => G ): AsyncResult<V, F | G>; flatRecoverAsync<F, G>( tryFun: (error: E) => Result<V, F> | AsyncResult<V, F>, catchFun?: (error: unknown) => G ): AsyncResult<V, F | G | unknown> { this.assertsThisIsResult(); if (this.isSuccess()) return AsyncResult.of(this); try { const result = tryFun(this.error); return Result.isResult(result) ? AsyncResult.of(result) : result; } catch (error: unknown) { return catchFun == null ? AsyncResult.failure(error) : AsyncResult.failure(catchFun(error)); } } tryFlatRecover<F>(tryFun: (error: E) => Result<V, F>): Result<V, unknown>; tryFlatRecover<F, G>( tryFun: (error: E) => Result<V, F>, catchFun: (error: unknown) => G ): Result<V, F | G>; tryFlatRecover<F, G>( tryFun: (error: E) => Result<V, F>, catchFun?: (error: unknown) => G ): Result<V, F | G | unknown> { return (catchFun == null ? Result.try(() => this.flatRecover(tryFun)) : Result.try(() => this.flatRecover(tryFun), catchFun) ).flatMap((it) => it); } mapError<F>(neverThrowFun: (error: E) => F): Result<V, F> { this.assertsThisIsResult(); return this.isFailure() ? Result.failure(neverThrowFun(this.error)) : this.castError(); } tryMapError<F>(tryFun: (error: E) => F): Result<V, unknown>; tryMapError<F, G>( tryFun: (error: E) => F, catchFun: (error: unknown) => G ): Result<V, F | G>; tryMapError<F, G>( tryFun: (error: E) => F, catchFun?: (error: unknown) => G ): Result<V, F | G | unknown> { return (catchFun == null ? Result.try(() => this.mapError(tryFun)) : Result.try(() => this.mapError(tryFun), catchFun) ).flatMap((it) => it); } } export class Success<V, E> extends AbstractResult<V, E> { constructor(readonly value: V) { super(); } readonly error = undefined; isSuccess(): this is Success<V, E> { return true; } isFailure(): this is Failure<V, E> { return false; } castError<F>(): Success<V, F> { return (this as any) as Success<V, F>; } } export class Failure<V, E> extends AbstractResult<V, E> { constructor(readonly error: E) { super(); } readonly value = undefined; isSuccess(): this is Success<V, E> { return false; } isFailure(): this is Failure<V, E> { return true; } castValue<U>(): Failure<U, E> { return (this as any) as Failure<U, E>; } } export type Result<V, E> = Success<V, E> | Failure<V, E>;