try-error Documentation
Migration Guides
Step-by-step guides for migrating from other error handling approaches to try-error
Migrating from try/catch
The most common migration scenario - moving from traditional try/catch blocks to try-error.
Before (try/catch)
1async function fetchUser(id: string): Promise<User | null> {
2 try {
3 const response = await fetch(`/api/users/${id}`);
4 if (!response.ok) {
5 throw new Error(`HTTP ${response.status}: ${response.statusText}`);
6 }
7 return await response.json();
8 } catch (error) {
9 console.error('Failed to fetch user:', error);
10 return null; // Lost error information
11 }
12}
13
14// Usage
15const user = await fetchUser('123');
16if (!user) {
17 // Can't tell if user doesn't exist or if there was an error
18 console.log('No user found or error occurred');
19}
After (try-error)
1import { tryAsync, isTryError } from 'try-error';
2
3async function fetchUser(id: string): Promise<TryResult<User, TryError>> {
4 return tryAsync(async () => {
5 const response = await fetch(`/api/users/${id}`);
6 if (!response.ok) {
7 throw new Error(`HTTP ${response.status}: ${response.statusText}`);
8 }
9 return await response.json();
10 });
11}
12
13// Usage
14const result = await fetchUser('123');
15if (isTryError(result)) {
16 console.error('Failed to fetch user:', result.message);
17 // Full error context available
18 console.log('Error details:', result.context);
19} else {
20 console.log('User found:', result.name);
21}
Migration Benefits
- • Explicit error handling - no silent failures
- • Rich error context preserved
- • Type-safe error handling
- • Better debugging information
- • Consistent error patterns across codebase
Migrating from neverthrow
If you're coming from neverthrow or similar Result libraries, try-error offers a more JavaScript-native approach.
Before (neverthrow)
1import { Result, ok, err } from 'neverthrow';
2
3async function fetchUser(id: string): Promise<Result<User, Error>> {
4 try {
5 const response = await fetch(`/api/users/${id}`);
6 if (!response.ok) {
7 return err(new Error(`HTTP ${response.status}`));
8 }
9 const user = await response.json();
10 return ok(user);
11 } catch (error) {
12 return err(error as Error);
13 }
14}
15
16// Usage - requires learning Result API
17const result = await fetchUser('123');
18result.match(
19 (user) => console.log('User:', user),
20 (error) => console.error('Error:', error.message)
21);
After (try-error)
1import { tryAsync, isTryError } from 'try-error';
2
3async function fetchUser(id: string): Promise<TryResult<User, TryError>> {
4 return tryAsync(async () => {
5 const response = await fetch(`/api/users/${id}`);
6 if (!response.ok) {
7 throw new Error(`HTTP ${response.status}`);
8 }
9 return await response.json();
10 });
11}
12
13// Usage - familiar JavaScript patterns
14const result = await fetchUser('123');
15if (isTryError(result)) {
16 console.error('Error:', result.message);
17} else {
18 console.log('User:', result);
19}
Key Differences
- • No need to learn monadic patterns (map, flatMap, etc.)
- • Uses familiar JavaScript error throwing
- • Simpler type system - just union types
- • Better integration with existing JavaScript code
- • Rich error context out of the box
Migrating from fp-ts Either
Moving from fp-ts Either to try-error for teams wanting functional error handling without the complexity.
Before (fp-ts)
1import { Either, left, right, fold } from 'fp-ts/Either';
2import { pipe } from 'fp-ts/function';
3import { TaskEither, tryCatch } from 'fp-ts/TaskEither';
4
5const fetchUser = (id: string): TaskEither<Error, User> =>
6 tryCatch(
7 () => fetch(`/api/users/${id}`).then(r => r.json()),
8 (reason) => new Error(String(reason))
9 );
10
11// Usage - requires fp-ts knowledge
12pipe(
13 await fetchUser('123')(),
14 fold(
15 (error) => console.error('Error:', error.message),
16 (user) => console.log('User:', user)
17 )
18);
After (try-error)
import { tryAsync, isTryError } from 'try-error';
const fetchUser = (id: string) =>
tryAsync(() => fetch(`/api/users/${id}`).then(r => r.json()));
// Usage - straightforward JavaScript
const result = await fetchUser('123');
if (isTryError(result)) {
console.error('Error:', result.message);
} else {
console.log('User:', result);
}
Step-by-Step Migration Process
1. Install try-error
2. Identify Migration Candidates
Look for these patterns in your codebase:
- • Functions that return null/undefined on error
- • try/catch blocks that swallow errors
- • Inconsistent error handling patterns
- • Functions that throw but callers don't handle errors
3. Start with Leaf Functions
Begin migration with functions that don't call other functions - typically API calls, file operations, or parsing functions.
4. Update Function Signatures
// Before
function parseJson(str: string): any | null
// After
function parseJson(str: string): TryResult<any, TryError>
5. Update Callers Gradually
Work your way up the call stack, updating callers to handle the new return types. You can mix try-error with existing patterns during migration.
6. Add Error Context
Enhance your error handling by adding context information to help with debugging.
Common Migration Patterns
Pattern 1: Null Returns → TryResult
Before
function findUser(id: string): User | null {
try {
return database.findById(id);
} catch {
return null;
}
}
After
function findUser(id: string): TryResult<User, TryError> {
return trySync(() => database.findById(id));
}
Pattern 2: Boolean Success → TryResult
Before
function saveUser(user: User): boolean {
try {
database.save(user);
return true;
} catch {
return false;
}
}
After
function saveUser(user: User): TryResult<void, TryError> {
return trySync(() => database.save(user));
}