Created
July 18, 2022 12:18
-
-
Save OliverBrotchie/4bc8168e02e56a84210667381f135b8d to your computer and use it in GitHub Desktop.
A L/R Result class
This file contains 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
function divide(left: number, right: number): Result<number, Error> { | |
if (right === 0) return Err("Divided by zero"); | |
return Ok(left / right); | |
} | |
class DivisionError extends Error { | |
constructor(message: string) { | |
super(message); | |
} | |
} | |
function typedDivide( | |
left: number, | |
right: number | |
): Result<number, DivisionError> { | |
if (right === 0) return Err(new DivisionError("Divided by zero")); | |
return Ok(left / right); | |
} | |
// Get the contained Ok value or bubble an error with the provided message. | |
const unwrappedResult = divide(2, 0).expect("Divided by zero"); | |
// Gets the contained Ok value or returns a provided default. | |
const result = divide(2, 0).unwrapOr(0); | |
// Map one type of Error to another | |
const mapedErrorResult = divide(2, 0).mapErr((e) => new TestError(e.message)); |
This file contains 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
/** | |
* A Rust-like Result class. | |
* | |
* Note: Please use either Ok or Err to construct Results. | |
* | |
* @example | |
* ``` | |
* function divide(left: number, right: number): Result<number, Error> { | |
* if (right === 0) return Err("Divided by zero"); | |
* | |
* return Ok(left / right); | |
* } | |
* | |
* ``` | |
*/ | |
export class Result<T, E extends Error> { | |
private val: T | E; | |
/** | |
* A constructor for a Result | |
* | |
* Note: Please use either Ok or Err to construct Results. | |
* @param input The value to wrap in a Result. | |
*/ | |
constructor(input: T | E) { | |
this.val = input; | |
} | |
/** | |
* Returns true if contained value isnt an error. | |
*/ | |
isOk(): boolean { | |
return !(this.val instanceof Error || Error.isPrototypeOf(this.val)); | |
} | |
/** | |
* Returns true if contained value is an error. | |
*/ | |
isErr(): boolean { | |
return this.val instanceof Error || Error.isPrototypeOf(this.val); | |
} | |
/** | |
* Returns the contained Ok value, consuming the self value. | |
* @throws An Error with a given message if contained value is not Ok. | |
* | |
* @param msg An error message to throw if contained value is an Error. | |
*/ | |
expect(msg: string): T { | |
if (this.isErr()) { | |
throw new Error(msg); | |
} | |
return this.val as T; | |
} | |
/** | |
* Returns the contained Ok value, consuming the self value. | |
* @throws An Error if contained value is not Ok. | |
*/ | |
unwrap(): T { | |
if (this.isErr()) { | |
throw new Error( | |
`Unwrap called ${(this.val as E).name} - ${(this.val as E).message}${ | |
(this.val as E)?.stack ? ", " + ((this.val as E).stack as string) : "" | |
}` | |
); | |
} | |
return this.val as T; | |
} | |
/** | |
* Returns the contained Error value, consuming the self value. | |
* @throws An Error if contained value is not an Error. | |
*/ | |
unwrapErr(): E { | |
if (this.isOk()) { | |
throw new Error( | |
`UnwrapError called on value - ${this.val as unknown as string}` | |
); | |
} | |
return this.val as E; | |
} | |
/** | |
* Returns the contained Ok value or a provided default. | |
* | |
* @param fallback A default value to return if contained value is an Error. | |
*/ | |
unwrapOr(fallback: T): T { | |
if (this.isErr()) { | |
return fallback; | |
} | |
return this.val as T; | |
} | |
/** | |
* Returns the contained Ok value or computes it from a closure. | |
* | |
* @param fn A function that computes a new value. | |
*/ | |
unwrapOrElse(fn: (input: E) => T): T { | |
if (this.isErr()) { | |
return fn(this.val as E); | |
} | |
return this.val as T; | |
} | |
/** | |
* Maps a Result<T, E> to Result<U, E> by applying a function to a contained Ok value, leaving an Error value untouched. | |
* | |
* @param fn A mapping function. | |
*/ | |
map<U>(fn: (input: T) => U): Result<U, E> { | |
if (this.isOk()) { | |
return new Result<U, E>(fn(this.val as T)); | |
} | |
return this as unknown as Result<U, E>; | |
} | |
/** | |
* Maps a Result<T, E> to Result<T, U> by applying a function to a contained Error value, leaving an Ok value untouched. | |
* | |
* @param fn A mapping function. | |
*/ | |
mapErr<U extends Error>(fn: (input: E) => U): Result<T, U> { | |
if (this.isOk()) { | |
return this as unknown as Result<T, U>; | |
} | |
return new Result<T, U>(fn(this.val as E)); | |
} | |
/** | |
* Returns the provided fallback (if Error), or applies a function to the contained value. | |
* | |
* @param fallback A defualt value | |
* @param fn A mapping function. | |
*/ | |
mapOr<U>(fallback: U, fn: (input: T) => U): U { | |
if (this.isOk()) { | |
return fn(this.val as T); | |
} | |
return fallback; | |
} | |
/** | |
* Returns `or` if the result is Error, otherwise returns self. | |
* | |
* @param or A alternative Result value | |
*/ | |
or(or: Result<T, E>): Result<T, E> { | |
if (this.isOk()) { | |
return this; | |
} | |
return or; | |
} | |
/** | |
* Returns contained value for use in matching. | |
* | |
* Note: Please only use this to match against in `if` or `swtich` statments. | |
* | |
* @example | |
* ``` | |
* function coolOrNice(input: Result<string, Error>): Result<void, Error> { | |
* switch (input.peek()) { | |
* case "cool": | |
* console.log("Input was the coolest!"); | |
* break; | |
* case "nice": | |
* console.log("Input was was the nicest!"); | |
* break | |
* default: | |
* return Err("Input neither cool nor nice."); | |
* } | |
* return Ok() | |
* } | |
* ``` | |
*/ | |
peek(): T | E { | |
return this.val; | |
} | |
/** | |
* Throws contained Errors, consuming the Result | |
* @throws The contained Error if contained value is an Error. | |
*/ | |
throw(): void { | |
if (this.isErr()) { | |
throw this.val; | |
} | |
} | |
} | |
/** | |
* Return a non-error value result. | |
* | |
* @param input a value that does not extend the `Error` type. | |
* @example | |
* ``` | |
* function divide(left: number, right: number): Result<number, Error> { | |
* if (right === 0) return Err("Divided by zero"); | |
* | |
* return Ok(left / right); | |
* } | |
* | |
* ``` | |
*/ | |
export function Ok<T, E extends Error>(input?: Exclude<T, E>) { | |
return new Result<T, E>(input as T); | |
} | |
/** | |
* Return a error result. | |
* | |
* @param input a value that extends the `Error` type. | |
* @example | |
* ``` | |
* function divide(left: number, right: number): Result<number, Error> { | |
* if (right === 0) return Err("Divided by zero"); | |
* | |
* return Ok(left / right); | |
* } | |
* | |
* ``` | |
*/ | |
export function Err<T, E extends Error>(input: string | E): Result<T, E> { | |
if (typeof input === "string") { | |
return new Result<T, Error>(new Error(input)) as Result<T, E>; | |
} | |
return new Result<T, E>(input); | |
} | |
/** | |
* Partition an array of Results into Ok values and Errors | |
* | |
* @param input An array of Results | |
* @example | |
* ``` | |
* const results = [Ok(2),Ok(16),Err("Something went wrong!")] | |
* | |
* partition(results) // {ok:[2, 16], err:[Error("Something went wrong!")]} | |
* | |
* ``` | |
*/ | |
export function partition<T, E extends Error>( | |
input: Array<Result<T, E>> | |
): { ok: Array<T>; err: Array<E> } { | |
return input.reduce( | |
(acc: { ok: Array<T>; err: Array<E> }, e) => { | |
if (e.isOk()) acc.ok.push(e.unwrap()); | |
else acc.err.push(e.unwrapErr()); | |
return acc; | |
}, | |
{ | |
ok: [], | |
err: [], | |
} | |
); | |
} | |
/** | |
* Run a closure in a `try`/`catch` and convert it into a Result | |
* @param fn The closure to run | |
* @returns The Result of the closure | |
*/ | |
export async function from<T>( | |
fn: (() => T) | (() => Promise<T>) | |
): Promise<Result<T, Error>> { | |
try { | |
return new Result<T, Error>(await fn()); | |
} catch (e: unknown) { | |
return new Result<T, Error>(e as Error); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment