Result
Explicit success and failure with typed errors
The Result type represents a value that can be either a success (Ok) or a failure (Err). It's the foundation of explicit error handling in @deessejs/fp.
Why Result?
TypeScript promises type safety, but traditional error handling with exceptions breaks that promise. When a function can throw, the type system can't help you handle errors, they're invisible until runtime.
The Problem with Exceptions
// This function CAN throw, but the type says it returns User
function processUser(data: string): User {
return JSON.parse(data); // No error in the type!
}
// Callers have no idea this can fail
try {
const user = processUser(data);
} catch (e) {
// But what if you forget?
}Problems:
- Errors are invisible in the type signature
- TypeScript can't enforce error handling at compile time
- Easy to forget try/catch, leading to unhandled exceptions
The Result Solution
With Result, errors become explicit in the type. The err() function wraps errors, and the error type E can be anything from simple strings to rich domain objects.
For structured domain errors with enrichment capabilities (notes, cause chaining, Zod validation), see the Error documentation.
import { ok, err, isOk, Result, error, ExtractError } from '@deessejs/fp';
import { z } from 'zod';
const ParseError = error({
name: 'ParseError',
schema: z.object({ message: z.string() }),
message: (args) => `Parse failed: ${args.message}`,
});
function parseUser(data: string): Result<User, ExtractError<typeof ParseError>> {
try {
return ok(JSON.parse(data));
} catch (e) {
return err(ParseError({ message: e instanceof Error ? e.message : 'Unknown' }));
}
}
const result = parseUser(data);
if (isOk(result)) {
console.log(result.value.name);
} else {
console.error('Failed:', result.error.message);
// Access enrichment: result.error.notes, result.error.cause, etc.
}Key concepts:
Result<User, Error>- Two generic types: success value (User) and error type (Error)ok(value)- Wraps success:{ ok: true, value: T }err(error)- Wraps failure:{ ok: false, error: E }isOk()- Type guard that narrows to the success branchresult.error- Access the error with its notes and cause chain (see Error)
Core Concepts
Ok and Err
Result<T, E> has two possible states:
| State | Description | Properties |
|---|---|---|
Ok<T> | Success | ok: true, value: T |
Err<E> | Failure | ok: false, error: E |
import { ok, err, Result } from '@deessejs/fp';
// Success
const success: Result<number, string> = ok(42);
// Failure
const failure: Result<number, string> = err('Something went wrong');The Error Type
The error type E is completely flexible. For rich domain errors with enrichment, see the Error documentation.
// String errors
const r1 = err('simple error');
// Structured errors
const r2 = err({ code: 'NOT_FOUND', message: 'User not found' });
// Custom error types
const r3 = err(new Error('something failed'));Reference
Creating Results
ok(value)
Creates a success result.
import { ok } from '@deessejs/fp';
ok(42); // Ok(42)
ok('hello'); // Ok('hello')
ok({ id: 1 }); // Ok({ id: 1 })err(error)
Creates a failure result.
import { err } from '@deessejs/fp';
err('error'); // Err('error')
err({ code: 404, message: 'Not found' }); // Err({ code: 404, message: 'Not found' })Type Guards
isOk(result)
Type guard that narrows to the success branch.
import { ok, err, isOk } from '@deessejs/fp';
const result = ok(42);
if (isOk(result)) {
console.log(result.value); // 42
}isErr(result)
Type guard that narrows to the failure branch.
import { ok, err, isErr } from '@deessejs/fp';
const result = err('oops');
if (isErr(result)) {
console.log(result.error); // 'oops'
}Transformation
map(result, fn)
Transforms the success value. Passes errors through unchanged.
import { ok, err, map } from '@deessejs/fp';
map(ok(2), x => x * 2); // Ok(4)
map(err('error'), x => x * 2); // Err('error')Also available as a method:
ok(2).map(x => x * 2); // Ok(4)flatMap(result, fn)
Chains operations that can fail. If Ok, applies the function. If Err, returns Err.
import { ok, err, flatMap } from '@deessejs/fp';
const findUser = (id: number) =>
id > 0 ? ok({ id, name: 'John' }) : err('Invalid id');
const getEmail = (user: { name: string }) =>
ok(user.name.toLowerCase() + '@example.com');
flatMap(ok(1), findUser); // Ok({ id: 1, name: 'John' })
flatMap(ok(-1), findUser); // Err('Invalid id')
flatMap(ok(1), x => flatMap(findUser(x.id), getEmail)); // Ok('john@example.com')Also available as a method:
ok(1).flatMap(findUser); // Ok({ id: 1, name: 'John' })mapErr(result, fn)
Transforms the error. Passes success through unchanged.
import { ok, err, mapErr } from '@deessejs/fp';
mapErr(err('not found'), e => new Error(e)); // Err(Error: 'not found')
mapErr(ok(42), e => new Error(e)); // Ok(42)Also available as a method:
err('not found').mapErr(e => new Error(e)); // Err(Error: 'not found')Extraction
getOrElse(result, defaultValue)
Returns the success value, or a default if Err.
import { ok, err, getOrElse } from '@deessejs/fp';
getOrElse(ok(42), 0); // 42
getOrElse(err('oops'), 0); // 0Also available as a method:
ok(42).getOrElse(0); // 42
err('oops').getOrElse(0); // 0getOrCompute(result, fn)
Returns the success value, or computes one lazily if Err. Useful for expensive fallbacks.
import { ok, err, getOrCompute } from '@deessejs/fp';
const expensive = () => { console.log('computing...'); return 0; };
getOrCompute(ok(42), expensive); // 42 (never logs)
getOrCompute(err('oops'), expensive); // logs 'computing...', returns 0Also available as a method:
err('oops').getOrCompute(() => 0); // 0Side Effects
tap(result, fn)
Executes a function on the value without changing it. Useful for logging.
import { ok, err, tap } from '@deessejs/fp';
tap(ok(42), x => console.log('Got:', x)); // Logs: 'Got: 42', returns Ok(42)
tap(err('oops'), x => console.log('Got:', x)); // Nothing logged, returns Err('oops')Also available as a method:
ok(42).tap(x => console.log(x)); // Ok(42)tapErr(result, fn)
Executes a function on the error without changing it.
import { ok, err, tapErr } from '@deessejs/fp';
tapErr(ok(42), e => console.error('Error:', e)); // Nothing logged
tapErr(err('oops'), e => console.error('Error:', e)); // Logs: 'Error: oops', returns Err('oops')Also available as a method:
err('oops').tapErr(e => console.error(e)); // Err('oops')Pattern Matching
match(result, okFn, errFn)
Handles both success and failure cases, returning a single value.
import { ok, err, match } from '@deessejs/fp';
match(ok(42), x => x * 2, () => 0); // 84
match(err('oops'), x => x * 2, () => 0); // 0Returns different types:
match(
ok('hello'),
value => value.length, // string → number
() => -1 // fallback
); // 5Also available as a method with object syntax:
ok(42).match({ onSuccess: x => x * 2, onError: () => 0 }); // 84
err('oops').match({ onSuccess: x => x * 2, onError: () => -1 }); // -1Combination
all(...results)
Combines multiple Results into one. Returns Ok with array of values if all succeed, or the first Err (fail-fast).
import { ok, err, all } from '@deessejs/fp';
all(ok(1), ok(2), ok(3)); // Ok([1, 2, 3])
all(ok(1), err('error'), ok(3)); // Err('error')Swapping
swap(result)
Swaps Ok and Err. Turns Result<T, E> into Result<E, T>.
import { ok, err, swap } from '@deessejs/fp';
swap(ok(42)); // Err(42)
swap(err('oops')); // Ok('oops')Also available as a method:
ok(42).swap(); // Err(42)
err('oops').swap(); // Ok('oops')Unwrapping
unwrap(result)
Extracts the value if Ok, throws the error if Err.
import { ok, err, unwrap } from '@deessejs/fp';
unwrap(ok(42)); // 42
unwrap(err('oops')); // throws 'oops'Also available as a method:
ok(42).unwrap(); // 42
err('oops').unwrap(); // throws 'oops'Warning: Use with caution. Throws defeat the purpose of explicit error handling.
Conversions
toNullable(result)
Converts to T | null.
import { ok, err, toNullable } from '@deessejs/fp';
toNullable(ok(42)); // 42
toNullable(err('x')); // nulltoUndefined(result)
Converts to T | undefined.
import { ok, err, toUndefined } from '@deessejs/fp';
toUndefined(ok(42)); // 42
toUndefined(err('x')); // undefinedMethod Chaining
Result objects support chaining for fluent transformations:
import { ok, err } from '@deessejs/fp';
const result = ok(5)
.map(x => x * 2) // Ok(10)
.map(x => x + 1) // Ok(11)
.flatMap(x => x > 10 ? ok(x) : err('too small')); // Err('too small')Real-World Examples
Input Validation
For richer error handling with domain-specific errors, notes, and cause chaining, see the Error documentation.
import { ok, err, flatMap, Result } from '@deessejs/fp';
type ValidationError = { field: string; message: string };
const validateAge = (age: number): Result<number, ValidationError> => {
if (age < 0) return err({ field: 'age', message: 'Must be positive' });
if (age > 150) return err({ field: 'age', message: 'Must be realistic' });
return ok(age);
};
const validateEmail = (email: string): Result<string, ValidationError> => {
if (!email.includes('@')) return err({ field: 'email', message: 'Invalid format' });
return ok(email);
};
const validateUser = (age: number, email: string): Result<{ age: number; email: string }, ValidationError> =>
flatMap(validateAge(age), () =>
flatMap(validateEmail(email), () =>
ok({ age, email })
)
);File Operations
import { readFileSync } from 'fs';
import { ok, err, getOrElse, Result } from '@deessejs/fp';
type FileError = 'NOT_FOUND' | 'PERMISSION_DENIED' | 'INVALID_JSON';
const readJsonFile = (path: string): Result<object, FileError> => {
try {
const content = readFileSync(path, 'utf-8');
return ok(JSON.parse(content));
} catch (e) {
if (e instanceof Error) {
if (e.message.includes('ENOENT')) return err('NOT_FOUND');
if (e.message.includes('EACCES')) return err('PERMISSION_DENIED');
}
return err('INVALID_JSON');
}
};
const config = getOrElse(readJsonFile('./config.json'), { defaults: true });Error Discrimination
type AppError =
| { type: 'validation'; field: string; message: string }
| { type: 'network'; code: number }
| { type: 'unauthorized' };
const handleError = (error: AppError): string => {
switch (error.type) {
case 'validation':
return `Invalid ${error.field}: ${error.message}`;
case 'network':
return `Network error: ${error.code}`;
case 'unauthorized':
return 'Please log in';
}
};Comparison with Alternatives
| Feature | @deessejs/fp | fp-ts | neverthrow |
|---|---|---|---|
| Bundle size | ~2KB | ~40KB | ~5KB |
| Learning curve | Low | High | Medium |
| Dependencies | 0 | Many | 0 |
See Also
- Maybe - For optional values (null/undefined)
- Try - For wrapping sync functions that might throw
- AsyncResult - For async operations with error handling
- Error - For structured errors with enrichment