-
-
Save seanaguinaga/b0c006aef408b42f96d559aa3a6e3b83 to your computer and use it in GitHub Desktop.
typescript implementation of the result pattern for functional error handling. provides type-safe alternatives to try/catch with monadic operations for composition. use this to make error handling explicit in your function signatures and avoid throwing exceptions
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 type Success<T> = { | |
readonly type: 'success' | |
readonly data: T | |
readonly error?: never | |
} | |
export type Failure<E> = { | |
readonly type: 'failure' | |
readonly data?: never | |
readonly error: E | |
} | |
export type Result<T, E = ErrorInfo> = Success<T> | Failure<E> | |
export type ErrorInfo = { | |
readonly message: string | |
readonly code?: string | |
readonly stack?: string | |
readonly cause?: unknown | |
} | |
export const isSuccess = <T, E>(result: Result<T, E>): result is Success<T> => { | |
return result.type === 'success' | |
} | |
export const isFailure = <T, E>(result: Result<T, E>): result is Failure<E> => { | |
return result.type === 'failure' | |
} | |
const createErrorInfo = (error: unknown): ErrorInfo => { | |
if (error instanceof Error) { | |
return { | |
message: error.message, | |
stack: error.stack, | |
cause: error.cause, | |
} | |
} | |
return { | |
message: String(error), | |
cause: error, | |
} | |
} | |
export const success = <T>(data: T): Success<T> => { | |
return { type: 'success', data } | |
} | |
export const failure = <E>(error: E): Failure<E> => { | |
return { type: 'failure', error } | |
} | |
export const tryCatch = async <T>( | |
promiseFn: () => Promise<T> | Promise<T> | |
): Promise<Result<T, ErrorInfo>> => { | |
try { | |
const data = await (typeof promiseFn === 'function' ? promiseFn() : promiseFn) | |
return success(data) | |
} catch (error) { | |
return failure(createErrorInfo(error)) | |
} | |
} | |
export const tryCatchSync = <T>(fn: () => T): Result<T, ErrorInfo> => { | |
try { | |
const data = fn() | |
return success(data) | |
} catch (error) { | |
return failure(createErrorInfo(error)) | |
} | |
} | |
export const map = <T, U, E>(result: Result<T, E>, fn: (data: T) => U): Result<U, E> => { | |
if (isSuccess(result)) { | |
return tryCatchSync(() => fn(result.data)) as Result<U, E> | |
} | |
return result | |
} | |
export const andThen = async <T, U, E>( | |
result: Result<T, E>, | |
fn: (data: T) => Promise<Result<U, E>> | Result<U, E> | |
): Promise<Result<U, E>> => { | |
if (isSuccess(result)) { | |
try { | |
return await fn(result.data) | |
} catch (error) { | |
return failure(createErrorInfo(error) as E) | |
} | |
} | |
return result | |
} | |
export const andThenSync = <T, U, E>( | |
result: Result<T, E>, | |
fn: (data: T) => Result<U, E> | |
): Result<U, E> => { | |
if (isSuccess(result)) { | |
try { | |
return fn(result.data) | |
} catch (error) { | |
return failure(createErrorInfo(error) as E) | |
} | |
} | |
return result | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment