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)

Traditional try/catch Approachtypescript
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)

try-error Approachtypescript
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)

neverthrow Result Patterntypescript
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)

try-error Equivalenttypescript
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)

fp-ts Either Patterntypescript
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)

try-error Equivalenttypescript
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

Installation
$pnpm add 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

Function Signature Updatestypescript
// 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

Null Return Patterntypescript
function findUser(id: string): User | null {
  try {
    return database.findById(id);
  } catch {
    return null;
  }
}

After

TryResult Patterntypescript
function findUser(id: string): TryResult<User, TryError> {
  return trySync(() => database.findById(id));
}

Pattern 2: Boolean Success → TryResult

Before

Boolean Success Patterntypescript
function saveUser(user: User): boolean {
  try {
    database.save(user);
    return true;
  } catch {
    return false;
  }
}

After

TryResult Patterntypescript
function saveUser(user: User): TryResult<void, TryError> {
  return trySync(() => database.save(user));
}

Migration Checklist

Related Pages

Quick Start

Get up and running with try-error quickly

View Quick Start →

Best Practices

Learn the recommended patterns and practices

View Best Practices →