tryError Documentation
Migration Guides
Step-by-step guides for migrating from other error handling approaches to tryError
Migrating from try/catch
The most common migration scenario - moving from traditional try/catch blocks to tryError.
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 (tryError)
1import { tryAsync, isTryError } from '@try-error/core';
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, tryError 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 (tryError)
1import { tryAsync, isTryError } from '@try-error/core';
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 tryError 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 (tryError)
import { tryAsync, isTryError } from '@try-error/core';
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 tryError
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 tryError 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));
}