-
-
Save adregan/1508255a5e84525958f9b4d80caffa39 to your computer and use it in GitHub Desktop.
Result
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| interface IResult<T, E> { | |
| isOk(): boolean; | |
| isErr(): boolean; | |
| map<U>(fn: (value: T) => U): Result<U, E>; | |
| flatMap<U, F>(fn: (value: T) => Result<U, F>): Result<U, E | F>; | |
| match<U, F>(params: { onOk: (value: T) => U; onErr: (error: E) => F }): U | F; | |
| unwrap(): T; | |
| unwrapOr<D>(defaultValue: D): T | D; | |
| } | |
| class Ok<T> implements IResult<T, never> { | |
| constructor(private readonly value: T) { } | |
| isOk(): this is Ok<T> { | |
| return true; | |
| } | |
| isErr(): this is never { | |
| return false; | |
| } | |
| map<U>(fn: (value: T) => U): Result<U, never> { | |
| return Result.ok(fn(this.value)); | |
| } | |
| flatMap<U, F>(fn: (value: T) => Result<U, F>): Result<U, never | F> { | |
| return fn(this.value); | |
| } | |
| match<U, F>({ | |
| onOk, | |
| }: { onOk: (value: T) => U; onErr: (error: never) => F }): U | F { | |
| return onOk(this.value); | |
| } | |
| unwrap(): T { | |
| return this.value; | |
| } | |
| unwrapOr<D>(_: D): T { | |
| return this.value; | |
| } | |
| } | |
| class Err<E> implements IResult<never, E> { | |
| constructor(private readonly error: E) { } | |
| isOk(): this is never { | |
| return false; | |
| } | |
| isErr(): this is Err<E> { | |
| return true; | |
| } | |
| map<U>(_: (value: never) => U): Result<U, E> { | |
| return this; | |
| } | |
| flatMap<U, F>(_: (value: never) => Result<U, F>): Result<never, E> { | |
| return this; | |
| } | |
| match<U, F>({ | |
| onErr, | |
| }: { onOk: (value: never) => U; onErr: (error: E) => F }): U | F { | |
| return onErr(this.error); | |
| } | |
| unwrap(): never { | |
| throw this.error; | |
| } | |
| unwrapOr<D>(defaultValue: D): D { | |
| return defaultValue; | |
| } | |
| } | |
| export type Result<T, E> = Ok<T> | Err<E>; | |
| export const Result = { | |
| ok: <T>(value: T): Ok<T> => new Ok(value), | |
| err: <E>(error: E): Err<E> => new Err(error), | |
| fromThrowable: <T>(fn: () => T): Result<T, Error> => { | |
| try { | |
| return Result.ok(fn()); | |
| } catch (error) { | |
| const err = error instanceof Error ? error : new Error(String(error)); | |
| return Result.err(err); | |
| } | |
| }, | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Result } from "./result"; | |
| describe("Result", () => { | |
| describe("creating", () => { | |
| describe("Result.ok()", () => { | |
| test("it returns an ok result", () => { | |
| const result = Result.ok("I'm good"); | |
| expect(result.isOk()).toBeTruthy(); | |
| }); | |
| test("an ok cannot be errored", () => { | |
| const result = Result.ok("I'm not bad"); | |
| expect(result.isErr()).toBeFalsy(); | |
| }); | |
| }); | |
| describe("Result.err()", () => { | |
| test("it returns an error result", () => { | |
| const result = Result.err("I'm bad"); | |
| expect(result.isErr()).toBeTruthy(); | |
| }); | |
| test("an error cannot be ok", () => { | |
| const result = Result.err("I'm not good"); | |
| expect(result.isOk()).toBeFalsy(); | |
| }); | |
| }); | |
| }); | |
| describe("result.match(onOk, onErr)", () => { | |
| test("it can pattern match the ok value to the onOk op", () => { | |
| const result: Result<string, Error> = Result.ok("hello"); | |
| const actual = result.match( | |
| (s: string) => `${s.toUpperCase()}!!`, | |
| (_) => 2, | |
| ); | |
| expect(actual).toEqual("HELLO!!"); | |
| }); | |
| test("it can pattern match the err value to the onErr op", () => { | |
| const result: Result<string, Error> = Result.err(Error("KABOOM")); | |
| expect( | |
| result.match( | |
| (_) => "I have a bad feeling about this.", | |
| (e: Error) => `Something went ${e.message}`, | |
| ), | |
| ).toEqual("Something went KABOOM"); | |
| }); | |
| }); | |
| describe("result.map(fn)", () => { | |
| test("an ok value can be transformed via map", () => { | |
| const result = Result.ok(2); | |
| const value = result.map((x): string => `${x * 2}`); | |
| expect(value).toEqual(4); | |
| }); | |
| test("an err value cannot be transformed via map", () => { | |
| const result = Result.err(Error("OH NO")); | |
| const value = result.map((x) => x * 3); | |
| expect(value).toEqual(null); | |
| }); | |
| }); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| export class Result<Ok, Err = Error> { | |
| #ok: Ok | null; | |
| #err: Err | null; | |
| private constructor({ ok, err }: { ok?: Ok; err?: Err }) { | |
| this.#ok = ok ?? null; | |
| this.#err = err ?? null; | |
| } | |
| static ok = <Ok>(ok: Ok): Result<Ok, never> => new Result({ ok }); | |
| static err = <Err>(err: Err): Result<never, Err> => new Result({ err }); | |
| isOk = (): this is Result<Ok, never> => this.#ok !== null; | |
| isErr = (): this is Result<never, Err> => this.#err !== null; | |
| match = <S, T>( | |
| onOk: (ok: Ok) => S, | |
| onErr: (err: Err) => T, | |
| ): this extends Result<Ok, never> ? S : T => | |
| //@ts-ignore | |
| this.isOk() ? onOk(this.#ok) : onErr(this.#err); | |
| map = <B>(fn: (a: Ok) => B): Result<B, never> | this => | |
| this.isOk() ? Result.ok(fn(this.#ok)) : this; | |
| getOr = <V extends unknown>(value: V = null): Ok | V => (this.isOk() ? this.#ok : value); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment