Last active
April 24, 2025 08:45
-
-
Save mohilcode/e9c5c19408ca7ecd6b7ae51f9b08d1f5 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
import { inspect } from 'util' | |
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' | |
} | |
export const createErrorInfo = (error: unknown): ErrorInfo => { | |
if (error instanceof Error) { | |
const baseInfo: ErrorInfo = { | |
message: error.message, | |
stack: error.stack, | |
cause: 'cause' in error ? error.cause : undefined, | |
} | |
return baseInfo | |
} | |
let message: string | |
try { | |
message = String(error) | |
} catch { | |
message = inspect(error) | |
} | |
return { | |
message: message, | |
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 function tryCatch<T>(promiseFn: () => Promise<T>): Promise<Result<T, ErrorInfo>> | |
export function tryCatch<T, E>( | |
promiseFn: () => Promise<T>, | |
mapErrorFn: (caughtError: unknown) => E | |
): Promise<Result<T, E>> | |
export async function tryCatch<T, E = ErrorInfo>( | |
promiseFn: () => Promise<T>, | |
mapErrorFn?: (caughtError: unknown) => E | |
): Promise<Result<T, E>> { | |
try { | |
const data = await promiseFn() | |
return success(data) | |
} catch (error) { | |
const errorHandler = mapErrorFn ?? (createErrorInfo as (caughtError: unknown) => E) | |
return failure(errorHandler(error)) | |
} | |
} | |
export function tryCatchSync<T>(fn: () => T): Result<T, ErrorInfo> | |
export function tryCatchSync<T, E>( | |
fn: () => T, | |
mapErrorFn: (caughtError: unknown) => E | |
): Result<T, E> | |
export function tryCatchSync<T, E = ErrorInfo>( | |
fn: () => T, | |
mapErrorFn?: (caughtError: unknown) => E | |
): Result<T, E> { | |
try { | |
const data = fn() | |
return success(data) | |
} catch (error) { | |
const errorHandler = mapErrorFn ?? (createErrorInfo as (caughtError: unknown) => E) | |
return failure(errorHandler(error)) | |
} | |
} | |
export function map<T, U>(result: Result<T, ErrorInfo>, fn: (data: T) => U): Result<U, ErrorInfo> | |
export function map<T, U, E>( | |
result: Result<T, E>, | |
fn: (data: T) => U, | |
mapErrorFn: (caughtError: unknown) => E | |
): Result<U, E> | |
export function map<T, U, E = ErrorInfo>( | |
result: Result<T, E>, | |
fn: (data: T) => U, | |
mapErrorFn?: (caughtError: unknown) => E | |
): Result<U, E> { | |
if (isSuccess(result)) { | |
try { | |
return success(fn(result.data)) | |
} catch (error) { | |
const errorHandler = mapErrorFn ?? (createErrorInfo as (caughtError: unknown) => E) | |
return failure(errorHandler(error)) | |
} | |
} | |
return result as Result<U, E> | |
} | |
export function andThen<T, U>( | |
result: Result<T, ErrorInfo>, | |
fn: (data: T) => Promise<Result<U, ErrorInfo>> | Result<U, ErrorInfo> | |
): Promise<Result<U, ErrorInfo>> | |
export function andThen<T, U, E>( | |
result: Result<T, E>, | |
fn: (data: T) => Promise<Result<U, E>> | Result<U, E>, | |
mapErrorFn: (caughtError: unknown) => E | |
): Promise<Result<U, E>> | |
export async function andThen<T, U, E = ErrorInfo>( | |
result: Result<T, E>, | |
fn: (data: T) => Promise<Result<U, E>> | Result<U, E>, | |
mapErrorFn?: (caughtError: unknown) => E | |
): Promise<Result<U, E>> { | |
if (isSuccess(result)) { | |
try { | |
const nextResult = await fn(result.data) | |
return nextResult | |
} catch (error) { | |
const errorHandler = mapErrorFn ?? (createErrorInfo as (caughtError: unknown) => E) | |
return failure(errorHandler(error)) | |
} | |
} | |
return result as Result<U, E> | |
} | |
export function andThenSync<T, U>( | |
result: Result<T, ErrorInfo>, | |
fn: (data: T) => Result<U, ErrorInfo> | |
): Result<U, ErrorInfo> | |
export function andThenSync<T, U, E>( | |
result: Result<T, E>, | |
fn: (data: T) => Result<U, E>, | |
mapErrorFn: (caughtError: unknown) => E | |
): Result<U, E> | |
export function andThenSync<T, U, E = ErrorInfo>( | |
result: Result<T, E>, | |
fn: (data: T) => Result<U, E>, | |
mapErrorFn?: (caughtError: unknown) => E | |
): Result<U, E> { | |
if (isSuccess(result)) { | |
try { | |
return fn(result.data) | |
} catch (error) { | |
const errorHandler = mapErrorFn ?? (createErrorInfo as (caughtError: unknown) => E) | |
return failure(errorHandler(error)) | |
} | |
} | |
return result as Result<U, E> | |
} | |
export const mapError = <T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F> => { | |
if (isFailure(result)) { | |
try { | |
return failure(fn(result.error)) | |
} catch (caughtError) { | |
console.error("Error occurred within mapError's mapping function:", caughtError) | |
throw caughtError | |
} | |
} | |
return result as Result<T, F> | |
} | |
export const getOrElse = <T, E>(result: Result<T, E>, defaultValue: T): T => { | |
if (isSuccess(result)) { | |
return result.data | |
} | |
return defaultValue | |
} | |
export const getOrElseL = <T, E>(result: Result<T, E>, defaultValueFn: (error: E) => T): T => { | |
if (isSuccess(result)) { | |
return result.data | |
} | |
return defaultValueFn(result.error) | |
} | |
export const match = <T, E, U>( | |
result: Result<T, E>, | |
onSuccess: (data: T) => U, | |
onFailure: (error: E) => U | |
): U => { | |
if (isSuccess(result)) { | |
return onSuccess(result.data) | |
} | |
return onFailure(result.error) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment