Created
October 29, 2025 23:35
-
-
Save mdippery/d719eeded458914ab1e3812c9f8f65a1 to your computer and use it in GitHub Desktop.
Rust's Result type in TypeScript
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
| /** | |
| * Abstract representation of success or failure. | |
| * | |
| * @remarks | |
| * Provides a fluid interface for chaining and working with errors that | |
| * is easier to read than long chains of null and undefined checks. | |
| */ | |
| export abstract class Result<T, E> { | |
| /** @return a new result representing a successful calculation. */ | |
| static ok<T, E = never>(value: T): Result<T, E> { | |
| return new Ok(value); | |
| } | |
| /** @return a new result representing a failure. */ | |
| static err<E, T = never>(value: E): Result<T, E> { | |
| return new Err(value); | |
| } | |
| /** True if the result is not an error. */ | |
| abstract isOk(): boolean; | |
| /** True if the result is an error. */ | |
| abstract isErr(): boolean; | |
| /** | |
| * Returns the wrapped result value. | |
| * | |
| * @remarks | |
| * This is an unsafe version of {@link unwrapOr} and @{link unwrapOrElse}. | |
| * Only use it when you are sure the result is not an error. | |
| * | |
| * @throws Error - exception if the result is an error. | |
| */ | |
| abstract unwrap(): T; | |
| /** | |
| * @returns the wrapped result value, or the default is the result is | |
| * an error. | |
| * | |
| * @param value - the default value to return if the result is an error. | |
| */ | |
| abstract unwrapOr(value: T): T; | |
| /** | |
| * @returns the wrapped result value, or the result of calling a function | |
| * if the result is an error. | |
| * | |
| * @param fn - the function used to generate a default value if the result | |
| * is an error. It accepts one parameter: the underlying error. | |
| */ | |
| abstract unwrapOrElse(fn: (error: E) => T): T; | |
| /** An async version of {@link unwrapOrElse}. */ | |
| abstract asyncUnwrapOrElse(fn: (error: E) => Promise<T>): Promise<T>; | |
| /** | |
| * Applies a function to the wrapped value and returns it in a new result, | |
| * or returns the underlying error unchanged in the result is an error. | |
| * | |
| * @param fn - applied to the underlying value, if not an error | |
| * | |
| * @returns a new result with `fn` applied to the underlying value, or | |
| * the error if the result was an error. | |
| */ | |
| abstract map<U>(fn: (value: T) => U): Result<U, E>; | |
| /** An async version of {@link map}. */ | |
| abstract asyncMap<U>(fn: (value: T) => Promise<U>): Promise<Result<U, E>>; | |
| /** | |
| * Maps the underlying error to a new error if the result is an error, | |
| * or returns the underlying value unchanged if the result is not an error. | |
| * | |
| * @param fn - applied to the error value to return a new error type. | |
| * | |
| * @return a result with the transformed error, or a result with the | |
| * underlying value unchanged if the result was not an error. | |
| */ | |
| abstract mapErr<F>(fn: (value: E) => F): Result<T, F>; | |
| /** An async version of {@link mapErr}. */ | |
| abstract asyncMapErr<F>(fn: (value: E) => Promise<F>): Promise<Result<T, F>>; | |
| /** | |
| * Applies a function to the underlying value if the result is not an | |
| * error, or returns `value` if the result is an error. | |
| * | |
| * @param value - the default value to return is the result is an error. | |
| * @param fn - applied to the underlying value of a non-error result. | |
| * | |
| * @return `fn` applied to the underlying value if the result is not an error, | |
| * or else `value`. | |
| */ | |
| abstract mapOr<U>(value: U, fn: (value: T) => U): U; | |
| /** An async version of {@link mapOr}. */ | |
| abstract asyncMapOr<U>(value: U, fn: (value: T) => Promise<U>): Promise<U>; | |
| /** | |
| * Applies `fn` to the underlying value if the result is not an error; else, | |
| * applies `errFn` to the underlying error if the result is an error. | |
| * | |
| * @param errFn - applied to the underlying error if the result is an error. | |
| * @param fn - applied to the underlying value if the result is not an error. | |
| * | |
| * @return a transformed value. | |
| */ | |
| abstract mapOrElse<U>(errFn: (error: E) => U, fn: (value: T) => U): U; | |
| /** An async version of {@link mapOrElse}. */ | |
| abstract asyncMapOrElse<U>( | |
| errFn: (error: E) => Promise<U>, | |
| fn: (value: T) => Promise<U>, | |
| ): Promise<U>; | |
| /** | |
| * @returns a new result with `fn` applied to the result's underlying value, | |
| * or the unchanged error if the result is an error. | |
| * | |
| * @remarks | |
| * This can be used to chain results together. | |
| * | |
| * @param fn - applied to the underlying value if the result is not an error. | |
| */ | |
| abstract andThen<U>(fn: (value: T) => Result<U, E>): Result<U, E>; | |
| /** An async version of {@link andThen}. */ | |
| abstract asyncAndThen<U>( | |
| fn: (value: T) => Promise<Result<U, E>>, | |
| ): Promise<Result<U, E>>; | |
| } | |
| class Ok<T, E> extends Result<T, E> { | |
| constructor(private readonly value: T) { | |
| super(); | |
| } | |
| override isOk(): boolean { | |
| return true; | |
| } | |
| override isErr(): boolean { | |
| return false; | |
| } | |
| override unwrap(): T { | |
| return this.value; | |
| } | |
| override unwrapOr(_value: T): T { | |
| return this.unwrap(); | |
| } | |
| override unwrapOrElse(_fn: (error: E) => T): T { | |
| return this.unwrap(); | |
| } | |
| override asyncUnwrapOrElse(_fn: (error: E) => Promise<T>): Promise<T> { | |
| return Promise.resolve(this.unwrap()); | |
| } | |
| override map<U>(fn: (value: T) => U): Result<U, E> { | |
| return new Ok(fn(this.unwrap())); | |
| } | |
| override async asyncMap<U>( | |
| fn: (value: T) => Promise<U>, | |
| ): Promise<Result<U, E>> { | |
| return new Ok(await fn(this.unwrap())); | |
| } | |
| override mapErr<F>(_fn: (value: E) => F): Result<T, F> { | |
| return new Ok(this.unwrap()); | |
| } | |
| override asyncMapErr<F>( | |
| _fn: (value: E) => Promise<F>, | |
| ): Promise<Result<T, F>> { | |
| return Promise.resolve(new Ok(this.unwrap())); | |
| } | |
| override mapOr<U>(_value: U, fn: (value: T) => U): U { | |
| return this.map(fn).unwrap(); | |
| } | |
| override async asyncMapOr<U>( | |
| _value: U, | |
| fn: (value: T) => Promise<U>, | |
| ): Promise<U> { | |
| const result = await this.asyncMap(fn); | |
| return result.unwrap(); | |
| } | |
| override mapOrElse<U>(_errFn: (error: E) => U, fn: (value: T) => U): U { | |
| return this.map(fn).unwrap(); | |
| } | |
| override async asyncMapOrElse<U>( | |
| _errFn: (error: E) => Promise<U>, | |
| fn: (value: T) => Promise<U>, | |
| ): Promise<U> { | |
| return (await this.asyncMap(fn)).unwrap(); | |
| } | |
| override andThen<U>(fn: (value: T) => Result<U, E>): Result<U, E> { | |
| return fn(this.unwrap()); | |
| } | |
| override async asyncAndThen<U>( | |
| fn: (value: T) => Promise<Result<U, E>>, | |
| ): Promise<Result<U, E>> { | |
| return await fn(this.unwrap()); | |
| } | |
| } | |
| class Err<T, E> extends Result<T, E> { | |
| constructor(private readonly value: E) { | |
| super(); | |
| } | |
| override isOk(): boolean { | |
| return false; | |
| } | |
| override isErr(): boolean { | |
| return true; | |
| } | |
| override unwrap(): T { | |
| throw new Error('is an Err, not Ok'); | |
| } | |
| override unwrapOr(value: T): T { | |
| return value; | |
| } | |
| override unwrapOrElse(fn: (error: E) => T): T { | |
| return fn(this.value); | |
| } | |
| override async asyncUnwrapOrElse(fn: (error: E) => Promise<T>): Promise<T> { | |
| return await fn(this.value); | |
| } | |
| override map<U>(_fn: (value: T) => U): Result<U, E> { | |
| return new Err(this.value); | |
| } | |
| override asyncMap<U>(_fn: (value: T) => Promise<U>): Promise<Result<U, E>> { | |
| return Promise.resolve(new Err(this.value)); | |
| } | |
| override mapErr<F>(fn: (value: E) => F): Result<never, F> { | |
| return new Err(fn(this.value)); | |
| } | |
| override async asyncMapErr<F>( | |
| fn: (value: E) => Promise<F>, | |
| ): Promise<Result<T, F>> { | |
| return new Err(await fn(this.value)); | |
| } | |
| override mapOr<U>(value: U, _fn: (value: never) => U): U { | |
| return value; | |
| } | |
| override asyncMapOr<U>(value: U, _fn: (value: T) => Promise<U>): Promise<U> { | |
| return Promise.resolve(value); | |
| } | |
| override mapOrElse<U>(errFn: (error: E) => U, _fn: (value: never) => U): U { | |
| return errFn(this.value); | |
| } | |
| override async asyncMapOrElse<U>( | |
| errFn: (error: E) => Promise<U>, | |
| _fn: (value: T) => Promise<U>, | |
| ): Promise<U> { | |
| return await errFn(this.value); | |
| } | |
| override andThen<U>(_fn: (value: T) => Result<U, E>): Result<U, E> { | |
| return new Err(this.value); | |
| } | |
| override asyncAndThen<U>( | |
| _fn: (value: T) => Promise<Result<U, E>>, | |
| ): Promise<Result<U, E>> { | |
| return Promise.resolve(new Err(this.value)); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment