Skip to content

Instantly share code, notes, and snippets.

@mdippery
Created October 29, 2025 23:35
Show Gist options
  • Select an option

  • Save mdippery/d719eeded458914ab1e3812c9f8f65a1 to your computer and use it in GitHub Desktop.

Select an option

Save mdippery/d719eeded458914ab1e3812c9f8f65a1 to your computer and use it in GitHub Desktop.
Rust's Result type in TypeScript
/**
* 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