Result Type Pattern
Type-safe error handling with Result type
Code
// Base Result type
type Result<T, E = Error> = Success<T> | Failure<E>;
class Success<T> {
readonly success = true;
constructor(readonly value: T) {}
map<U>(fn: (value: T) => U): Success<U> {
return new Success(fn(this.value));
}
flatMap<U, E>(fn: (value: T) => Result<U, E>): Result<U, E> {
return fn(this.value);
}
unwrap(): T {
return this.value;
}
unwrapOr(_default: T): T {
return this.value;
}
}
class Failure<E> {
readonly success = false;
constructor(readonly error: E) {}
map<U>(_fn: (value: any) => U): Failure<E> {
return this;
}
flatMap<U, F>(_fn: (value: any) => Result<U, F>): Failure<E> {
return this;
}
unwrap(): never {
throw this.error;
}
unwrapOr<T>(defaultValue: T): T {
return defaultValue;
}
}
// Helper functions
function success<T>(value: T): Success<T> {
return new Success(value);
}
function failure<E>(error: E): Failure<E> {
return new Failure(error);
}
// Try-catch wrapper
function tryCatch<T, E = Error>(
fn: () => T,
errorHandler?: (error: unknown) => E
): Result<T, E> {
try {
return success(fn());
} __TOKEN_53__ (error) {
return failure(errorHandler ? errorHandler(error) : error as E);
}
}
// Async version
async function tryCatchAsync<T, E = Error>(
fn: () => Promise<T>,
errorHandler?: (error: unknown) => E
): Promise<Result<T, E>> {
try {
return success(await fn());
} __TOKEN_60__ (error) {
return failure(errorHandler ? errorHandler(error) : error as E);
}
}
// Combine multiple results
function combine<T1, T2, E>(r1: Result<T1, E>, r2: Result<T2, E>): Result<[T1, T2], E>;
function combine<T1, T2, T3, E>(r1: Result<T1, E>, r2: Result<T2, E>, r3: Result<T3, E>): Result<[T1, T2, T3], E>;
function combine<E>(...results: Result<any, E>[]): Result<any[], E> {
const values: any[] = [];
__TOKEN_66__ (const result of results) {
__TOKEN_69__ (!result.success) {
return result as Failure<E>;
}
values.push(result.value);
}
return success(values);
}
// Usage examples
// Basic usage
function divide(a: number, b: number): Result<number, string> {
__TOKEN_73__ (b === 0) {
return failure('Division by zero');
}
return success(a / b);
}
const result1 = divide(10, 2);
__TOKEN_77__ (result1.success) {
console.log('Result:', result1.value); // 5
}
// Chaining operations
const complexResult = divide(10, 2)
.map(x => x * 3)
.flatMap(x => divide(x, 5));
// Error handling
function parseJSONSafe<T>(json: string): Result<T, string> {
return tryCatch(
() => JSON.parse(json),
(error) => `JSON parse error: ${error}`
);
}
// Async example
async function fetchUser(id: number): Promise<Result<User, string>> {
return tryCatchAsync(
__TOKEN_84__ () => {
const response = await fetch(`/api/users/${id}`);
__TOKEN_87__ (!response.ok) throw new Error('Network error');
return response.json();
},
(error) => `Failed to fetch user: ${error}`
);
}
// Railway-oriented programming
class UserService {
validateInput(input: any): Result<UserInput, string> {
// validation logic
return success(input);
}
async processUser(input: UserInput): Promise<Result<User, string>> {
// processing logic
return success({ id: 1, name: 'John' });
}
async createUser(rawInput: any): Promise<Result<User, string>> {
return this.validateInput(rawInput)
.flatMap(input => this.processUser(input));
}
}