Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save WomB0ComB0/e98ffe77bf85318fe7fac62b79fa9c29 to your computer and use it in GitHub Desktop.
Save WomB0ComB0/e98ffe77bf85318fe7fac62b79fa9c29 to your computer and use it in GitHub Desktop.
The Result pattern is a functional programming approach that helps you handle success and failure states explicitly, making your code more predictable and easier to maintain.
type Success<T> = {
readonly success: true;
readonly value: T;
};
type Failure<E> = {
readonly success: false;
readonly error: E;
};
type Result<T, E> = Success<T> | Failure<E>;
/**
* Creates a successful result
* @param value The value to wrap in a success result
*/
const success = <T>(value: T): Success<T> =>
Object.freeze({ success: true, value });
/**
* Creates a failed result
* @param error The error to wrap in a failure result
*/
const failure = <E>(error: E): Failure<E> =>
Object.freeze({ success: false, error });
type ExtractAsyncArgs<Args extends Array<any>> = Args extends Array<infer PotentialArgTypes> ? [PotentialArgTypes] : []
export const catchError = async <Args extends Array<any>, ReturnType>(
asyncFunction: (...args: ExtractAsyncArgs<Args>) => Promise<ReturnType>,
...args: ExtractAsyncArgs<Args>
): Promise<Result<ReturnType, Error>> => {
try {
const result = await asyncFunction(...args);
return success(result);
} catch (error) {
return failure(error as Error);
}
}
/**
* Maps a successful result to a new value
* @param fn Mapping function to apply to the successful value
*/
export const map = <T, U, E>(
fn: (value: T) => U
): (result: Result<T, E>) => Result<U, E> =>
(result) =>
result.success ? success(fn(result.value)) : result;
/**
* Chains a result-returning function after a successful result
* @param fn Function that returns a new result
*/
export const bind = <T, U, E>(
fn: (value: T) => Result<U, E>
): (result: Result<T, E>) => Result<U, E> =>
(result) =>
result.success ? fn(result.value) : result;
/**
* Applies a series of functions to an input value, short-circuiting on the first failure
* @param input Initial input value
* @param functions Array of functions to apply sequentially
* @returns Final result after applying all functions or first encountered failure
*/
export const railway = <TInput, TOutput, E>(
input: TInput,
...functions: Array<(input: any) => Result<any, E>>
): Result<TOutput, E> => {
return functions.reduce<Result<any, E>>(
(result, fn) => result.success ? fn(result.value) : result,
success(input)
);
};
/**
* Recovers from a failure by applying a function to the error
* @param fn Function to handle the error and return a new result
*/
export const recover = <T, E1, E2>(
fn: (error: E1) => Result<T, E2>
): (result: Result<T, E1>) => Result<T, E2> =>
(result) =>
result.success ? result : fn(result.error);
/**
* Taps into a result chain for side effects without modifying the value
* @param fn Side effect function to execute on success
*/
export const tap = <T, E>(
fn: (value: T) => void
): (result: Result<T, E>) => Result<T, E> =>
(result) => {
if (result.success) {
fn(result.value);
}
return result;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment