try-error Documentation
TryResult vs Exceptions
A detailed comparison between try-error and traditional exception handling
Quick Comparison
Aspect | try-error | Exceptions |
---|---|---|
Type Safety | ✅ Errors visible in types | ❌ Invisible to type system |
Performance | ✅ Zero overhead success path | ❌ Stack unwinding cost |
Explicit Handling | ✅ Must check before use | ❌ Easy to forget catch |
Control Flow | ✅ Predictable returns | ❌ Unpredictable jumps |
Learning Curve | ⚠️ New patterns to learn | ✅ Familiar to most devs |
Ecosystem | ⚠️ Newer approach | ✅ Universal support |
Detailed Comparisons
Type Safety
❌ Exceptions
Exception-based Approachtypescript
// No indication of possible errors
function parseJSON(input: string): object {
return JSON.parse(input); // Might throw!
}
// Caller has no type-level guidance
const result = parseJSON(userInput);
// What if it throws? TypeScript can't help
✅ try-error
try-error Approachtypescript
// Clear error possibility in return type
function parseJSON(input: string): TryResult<object, TryError> {
return trySync(() => JSON.parse(input));
}
// Caller must handle both cases
const result = parseJSON(userInput);
if (isTryError(result)) {
// Handle error case
} else {
// Use successful result
}
Performance
Performance Impact: Exception throwing involves stack unwinding, which is expensive. try-error has zero overhead for success cases and minimal overhead for errors.
Performance Comparisontypescript
1// Exception performance cost
2try {
3 const result = riskyOperation(); // If this throws...
4 return result;
5} catch (error) {
6 // Stack unwinding happened here (expensive)
7 return null;
8}
9
10// try-error performance
11const result = trySync(() => riskyOperation());
12if (isTryError(result)) {
13 // No stack unwinding, just a return value
14 return null;
15}
16return result; // Zero overhead for success
Error Propagation
Exceptions
Exception Propagationtypescript
async function processData() {
try {
const step1 = await fetchData();
const step2 = await processStep1(step1);
const step3 = await processStep2(step2);
return step3;
} catch (error) {
// Which step failed? Hard to tell
console.error('Something failed:', error);
throw error;
}
}
try-error
try-error Propagationtypescript
async function processData() {
const step1 = await tryAsync(() => fetchData());
if (isTryError(step1)) return step1;
const step2 = await tryAsync(() => processStep1(step1));
if (isTryError(step2)) return step2;
const step3 = await tryAsync(() => processStep2(step2));
return step3; // Clear which step succeeded/failed
}
When to Use Each Approach
✅ Use try-error for:
- • New code where you control the API
- • Operations that commonly fail (network, parsing, validation)
- • When you want explicit error handling
- • Performance-critical code
- • When type safety is important
- • Complex error handling logic
✅ Use exceptions for:
- • Integrating with existing exception-based APIs
- • Truly exceptional conditions
- • When you need to bubble up through many layers
- • Library code that needs to match ecosystem patterns
- • Programming errors (assertions)
- • Legacy codebases where consistency matters
Hybrid Approaches
You don't have to choose one or the other. Here are strategies for using both:
Boundary Pattern
Boundary Patterntypescript
1// Use try-error internally, exceptions at boundaries
2class UserService {
3 // Internal methods use try-error
4 private async fetchUserData(id: string): Promise<TryResult<User, TryError>> {
5 const result = await tryAsync(() => this.api.getUser(id));
6 return result;
7 }
8
9 // Public API uses exceptions for compatibility
10 async getUser(id: string): Promise<User> {
11 const result = await this.fetchUserData(id);
12 if (isTryError(result)) {
13 throw new Error(`Failed to fetch user: ${result.message}`);
14 }
15 return result;
16 }
17}
Adapter Pattern
Adapter Patterntypescript
// Wrap exception-based APIs with try-error
function safeFetch(url: string): Promise<TryResult<Response, TryError>> {
return tryAsync(() => fetch(url));
}
// Convert try-error to exceptions when needed
function throwingParse(json: string): object {
const result = trySync(() => JSON.parse(json));
if (isTryError(result)) {
throw new Error(result.message);
}
return result;
}
Performance Considerations
Benchmark Results: In typical scenarios, try-error shows 2-10x better performance for error cases and identical performance for success cases compared to try/catch.
Performance Benchmark Exampletypescript
1// Performance comparison example
2// Exception version: ~100ms for 1000 errors
3function parseWithExceptions(inputs: string[]) {
4 const results = [];
5 for (const input of inputs) {
6 try {
7 results.push(JSON.parse(input));
8 } catch {
9 results.push(null);
10 }
11 }
12 return results;
13}
14
15// try-error version: ~20ms for 1000 errors
16function parseWithTryError(inputs: string[]) {
17 const results = [];
18 for (const input of inputs) {
19 const result = trySync(() => JSON.parse(input));
20 results.push(isTryError(result) ? null : result);
21 }
22 return results;
23}