try-error Documentation
Utilities API
API reference for try-error utility functions and helpers
Type Guards
Type guard functions help TypeScript narrow types and provide runtime type checking for try-error results.
isTryError
Check if a value is a TryError instance with proper type narrowing.
1import { isTryError } from 'try-error';
2
3function isTryError<T = any>(value: unknown): value is TryError<T>
4
5// Usage examples
6const result = await tryAsync(() => fetchUser('123'));
7
8if (isTryError(result)) {
9 // TypeScript knows result is TryError
10 console.error('Error:', result.message);
11 console.error('Type:', result.type);
12 console.error('Context:', result.context);
13} else {
14 // TypeScript knows result is User
15 console.log('User:', result.name);
16}
17
18// Generic type checking
19function handleResult<T>(result: TryResult<T, TryError>) {
20 if (isTryError(result)) {
21 return { success: false, error: result };
22 }
23 return { success: true, data: result };
24}
isTrySuccess
Check if a TryResult represents a successful operation.
1import { isTrySuccess } from 'try-error';
2
3function isTrySuccess<T, E extends TryError>(
4 result: TryResult<T, E>
5): result is T
6
7// Usage examples
8const results = await Promise.all([
9 tryAsync(() => fetchUser('1')),
10 tryAsync(() => fetchUser('2')),
11 tryAsync(() => fetchUser('3'))
12]);
13
14const successfulResults = results.filter(isTrySuccess);
15// TypeScript knows successfulResults is User[]
16
17const users = results
18 .filter(isTrySuccess)
19 .map(user => user.name); // Type-safe access
20
21// Conditional processing
22function processResults<T>(results: TryResult<T, TryError>[]) {
23 const successful = results.filter(isTrySuccess);
24 const failed = results.filter(isTryError);
25
26 return {
27 successful,
28 failed,
29 successRate: successful.length / results.length
30 };
31}
hasErrorType
Check if an error has a specific type with type narrowing.
1import { hasErrorType } from 'try-error';
2
3function hasErrorType<T extends string>(
4 error: TryError,
5 type: T
6): error is TryError & { type: T }
7
8// Usage examples
9const result = await tryAsync(() => validateUser(userData));
10
11if (isTryError(result)) {
12 if (hasErrorType(result, 'ValidationError')) {
13 // Handle validation errors specifically
14 console.log('Validation failed:', result.context.field);
15 } else if (hasErrorType(result, 'NetworkError')) {
16 // Handle network errors
17 console.log('Network issue:', result.context.status);
18 } else {
19 // Handle other errors
20 console.log('Unknown error:', result.type);
21 }
22}
23
24// Multiple type checking
25function isRetryableError(error: TryError): boolean {
26 return hasErrorType(error, 'NetworkError') ||
27 hasErrorType(error, 'TimeoutError') ||
28 hasErrorType(error, 'RateLimitError');
29}
Result Transformers
Transform and manipulate try-error results with functional programming patterns.
mapResult
Transform successful results while preserving errors.
1import { mapResult } from 'try-error';
2
3function mapResult<T, U, E extends TryError>(
4 result: TryResult<T, E>,
5 transform: (value: T) => U
6): TryResult<U, E>
7
8// Usage examples
9const userResult = await tryAsync(() => fetchUser('123'));
10
11// Transform user to display name
12const nameResult = mapResult(userResult, user => user.name);
13
14// Chain transformations
15const upperNameResult = mapResult(nameResult, name => name.toUpperCase());
16
17// Complex transformations
18const userSummaryResult = mapResult(userResult, user => ({
19 id: user.id,
20 displayName: `${user.firstName} ${user.lastName}`,
21 isActive: user.status === 'active',
22 memberSince: new Date(user.createdAt).getFullYear()
23}));
24
25// Async transformations
26async function mapResultAsync<T, U, E extends TryError>(
27 result: TryResult<T, E>,
28 transform: (value: T) => Promise<U>
29): Promise<TryResult<U, E>> {
30 if (isTryError(result)) {
31 return result;
32 }
33 return tryAsync(() => transform(result));
34}
mapError
Transform errors while preserving successful results.
1import { mapError } from 'try-error';
2
3function mapError<T, E1 extends TryError, E2 extends TryError>(
4 result: TryResult<T, E1>,
5 transform: (error: E1) => E2
6): TryResult<T, E2>
7
8// Usage examples
9const result = await tryAsync(() => fetchUser('123'));
10
11// Transform network errors to user-friendly messages
12const friendlyResult = mapError(result, error => {
13 if (hasErrorType(error, 'NetworkError')) {
14 return createTryError(
15 'UserFriendlyError',
16 'Unable to load user data. Please try again.',
17 { originalError: error.type }
18 );
19 }
20 return error;
21});
22
23// Add context to errors
24const enrichedResult = mapError(result, error =>
25 enrichError(error, {
26 userId: '123',
27 timestamp: Date.now(),
28 userAgent: navigator.userAgent
29 })
30);
31
32// Convert errors to different types
33const apiResult = mapError(result, error => {
34 const statusCode = getHttpStatusForError(error);
35 return createTryError('ApiError', error.message, {
36 statusCode,
37 originalType: error.type,
38 context: error.context
39 });
40});
flatMapResult
Chain operations that return TryResult, flattening nested results.
1import { flatMapResult } from 'try-error';
2
3function flatMapResult<T, U, E extends TryError>(
4 result: TryResult<T, E>,
5 transform: (value: T) => TryResult<U, E>
6): TryResult<U, E>
7
8// Usage examples
9const userResult = await tryAsync(() => fetchUser('123'));
10
11// Chain dependent operations
12const profileResult = flatMapResult(userResult, user =>
13 tryAsync(() => fetchUserProfile(user.id))
14);
15
16// Multiple chained operations
17const fullUserData = await tryAsync(() => fetchUser('123'))
18 .then(result => flatMapResult(result, user =>
19 tryAsync(() => fetchUserProfile(user.id))
20 ))
21 .then(result => flatMapResult(result, profile =>
22 tryAsync(() => fetchUserPreferences(profile.userId))
23 ));
24
25// Conditional chaining
26const conditionalResult = flatMapResult(userResult, user => {
27 if (user.isActive) {
28 return tryAsync(() => fetchActiveUserData(user.id));
29 } else {
30 return tryAsync(() => fetchInactiveUserData(user.id));
31 }
32});
33
34// Error propagation in chains
35async function processUserWorkflow(userId: string) {
36 return tryAsync(() => fetchUser(userId))
37 .then(result => flatMapResult(result, user =>
38 tryAsync(() => validateUser(user))
39 ))
40 .then(result => flatMapResult(result, user =>
41 tryAsync(() => processUser(user))
42 ))
43 .then(result => flatMapResult(result, processed =>
44 tryAsync(() => saveProcessedUser(processed))
45 ));
46}
Result Combinators
Combine multiple try-error results with various strategies.
combineResults
Combine multiple results into a single result containing all successful values.
1import { combineResults } from 'try-error';
2
3function combineResults<T extends readonly TryResult<any, any>[]>(
4 results: T
5): TryResult<UnwrapResults<T>, TryError>
6
7// Usage examples
8const userResult = await tryAsync(() => fetchUser('123'));
9const profileResult = await tryAsync(() => fetchProfile('123'));
10const prefsResult = await tryAsync(() => fetchPreferences('123'));
11
12// Combine all results
13const combinedResult = combineResults([userResult, profileResult, prefsResult]);
14
15if (isTrySuccess(combinedResult)) {
16 const [user, profile, preferences] = combinedResult;
17 // All operations succeeded
18 console.log('User data loaded:', { user, profile, preferences });
19} else {
20 // At least one operation failed
21 console.error('Failed to load complete user data:', combinedResult.message);
22}
23
24// Named combination
25const namedResult = combineResults({
26 user: userResult,
27 profile: profileResult,
28 preferences: prefsResult
29});
30
31if (isTrySuccess(namedResult)) {
32 const { user, profile, preferences } = namedResult;
33 // Type-safe destructuring
34}
35
36// Partial success handling
37function combineWithPartialSuccess<T extends Record<string, TryResult<any, any>>>(
38 results: T
39): { successful: Partial<UnwrapResults<T>>; failed: TryError[] } {
40 const successful: any = {};
41 const failed: TryError[] = [];
42
43 for (const [key, result] of Object.entries(results)) {
44 if (isTrySuccess(result)) {
45 successful[key] = result;
46 } else {
47 failed.push(result);
48 }
49 }
50
51 return { successful, failed };
52}
raceResults
Return the first successful result or all errors if all fail.
1import { raceResults } from 'try-error';
2
3function raceResults<T>(
4 results: Promise<TryResult<T, TryError>>[]
5): Promise<TryResult<T, TryError[]>>
6
7// Usage examples
8const primaryResult = tryAsync(() => fetchFromPrimaryAPI('123'));
9const fallbackResult = tryAsync(() => fetchFromFallbackAPI('123'));
10const cacheResult = tryAsync(() => fetchFromCache('123'));
11
12// Race for first success
13const fastestResult = await raceResults([
14 primaryResult,
15 fallbackResult,
16 cacheResult
17]);
18
19if (isTrySuccess(fastestResult)) {
20 console.log('Got data from fastest source:', fastestResult);
21} else {
22 console.error('All sources failed:', fastestResult);
23}
24
25// Timeout with fallback
26async function fetchWithTimeout<T>(
27 operation: () => Promise<T>,
28 timeoutMs: number,
29 fallback: () => Promise<T>
30): Promise<TryResult<T, TryError>> {
31 const timeoutPromise = new Promise<TryResult<T, TryError>>(resolve => {
32 setTimeout(() => {
33 resolve(createTryError('TimeoutError', `Operation timed out after ${timeoutMs}ms`));
34 }, timeoutMs);
35 });
36
37 return raceResults([
38 tryAsync(operation),
39 timeoutPromise,
40 tryAsync(fallback)
41 ]);
42}
sequenceResults
Execute operations in sequence, stopping at the first error.
1import { sequenceResults } from 'try-error';
2
3function sequenceResults<T>(
4 operations: (() => Promise<TryResult<T, TryError>>)[]
5): Promise<TryResult<T[], TryError>>
6
7// Usage examples
8const operations = [
9 () => tryAsync(() => validateInput(data)),
10 () => tryAsync(() => processData(data)),
11 () => tryAsync(() => saveData(data)),
12 () => tryAsync(() => notifySuccess(data))
13];
14
15const sequenceResult = await sequenceResults(operations);
16
17if (isTrySuccess(sequenceResult)) {
18 console.log('All operations completed:', sequenceResult);
19} else {
20 console.error('Sequence failed at step:', sequenceResult);
21}
22
23// Pipeline with transformations
24async function processPipeline<T>(
25 input: T,
26 steps: ((input: any) => Promise<TryResult<any, TryError>>)[]
27): Promise<TryResult<any, TryError>> {
28 let current: TryResult<any, TryError> = input;
29
30 for (const step of steps) {
31 if (isTryError(current)) {
32 return current;
33 }
34 current = await step(current);
35 }
36
37 return current;
38}
39
40// Usage
41const pipelineResult = await processPipeline(userData, [
42 data => tryAsync(() => validateUser(data)),
43 data => tryAsync(() => enrichUserData(data)),
44 data => tryAsync(() => saveUser(data)),
45 data => tryAsync(() => sendWelcomeEmail(data))
46]);
Utility Helpers
Additional helper functions for common try-error patterns and operations.
unwrapOr
Extract the value from a result or return a default value.
1import { unwrapOr } from 'try-error';
2
3function unwrapOr<T>(result: TryResult<T, TryError>, defaultValue: T): T
4
5// Usage examples
6const userResult = await tryAsync(() => fetchUser('123'));
7
8// Simple default
9const user = unwrapOr(userResult, { id: '123', name: 'Unknown User' });
10
11// Computed default
12const userName = unwrapOr(
13 mapResult(userResult, u => u.name),
14 'Anonymous'
15);
16
17// Function-based default
18function unwrapOrElse<T>(
19 result: TryResult<T, TryError>,
20 getDefault: (error: TryError) => T
21): T {
22 if (isTrySuccess(result)) {
23 return result;
24 }
25 return getDefault(result);
26}
27
28const userWithFallback = unwrapOrElse(userResult, error => {
29 console.warn('Failed to fetch user:', error.message);
30 return createGuestUser();
31});
32
33// Nullable unwrap
34function unwrapOrNull<T>(result: TryResult<T, TryError>): T | null {
35 return isTrySuccess(result) ? result : null;
36}
37
38const maybeUser = unwrapOrNull(userResult);
39if (maybeUser) {
40 console.log('User found:', maybeUser.name);
41}
retry
Retry operations with configurable strategies and backoff.
1import { retry, RetryOptions } from 'try-error';
2
3interface RetryOptions {
4 maxAttempts: number;
5 delay?: number;
6 backoff?: 'linear' | 'exponential';
7 shouldRetry?: (error: TryError) => boolean;
8}
9
10function retry<T>(
11 operation: () => Promise<T>,
12 options: RetryOptions
13): Promise<TryResult<T, TryError>>
14
15// Usage examples
16const result = await retry(
17 () => fetchUser('123'),
18 {
19 maxAttempts: 3,
20 delay: 1000,
21 backoff: 'exponential'
22 }
23);
24
25// Custom retry logic
26const customRetryResult = await retry(
27 () => processPayment(paymentData),
28 {
29 maxAttempts: 5,
30 delay: 500,
31 shouldRetry: (error) => {
32 // Only retry on network errors or rate limits
33 return hasErrorType(error, 'NetworkError') ||
34 hasErrorType(error, 'RateLimitError');
35 }
36 }
37);
38
39// Retry with jitter
40async function retryWithJitter<T>(
41 operation: () => Promise<T>,
42 options: RetryOptions & { jitter?: boolean }
43): Promise<TryResult<T, TryError>> {
44 let lastError: TryError;
45
46 for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
47 const result = await tryAsync(operation);
48
49 if (isTrySuccess(result)) {
50 return result;
51 }
52
53 lastError = result;
54
55 if (attempt < options.maxAttempts &&
56 (!options.shouldRetry || options.shouldRetry(result))) {
57 const delay = calculateDelay(attempt, options);
58 const jitteredDelay = options.jitter ?
59 delay * (0.5 + Math.random() * 0.5) : delay;
60 await sleep(jitteredDelay);
61 }
62 }
63
64 return lastError!;
65}
timeout
Add timeout functionality to async operations.
1import { timeout } from 'try-error';
2
3function timeout<T>(
4 operation: () => Promise<T>,
5 timeoutMs: number,
6 timeoutMessage?: string
7): Promise<TryResult<T, TryError>>
8
9// Usage examples
10const result = await timeout(
11 () => fetchLargeDataset(),
12 5000, // 5 second timeout
13 'Data fetch timed out'
14);
15
16// Timeout with cleanup
17async function timeoutWithCleanup<T>(
18 operation: (signal: AbortSignal) => Promise<T>,
19 timeoutMs: number
20): Promise<TryResult<T, TryError>> {
21 const controller = new AbortController();
22
23 const timeoutId = setTimeout(() => {
24 controller.abort();
25 }, timeoutMs);
26
27 try {
28 const result = await tryAsync(() => operation(controller.signal));
29 clearTimeout(timeoutId);
30 return result;
31 } catch (error) {
32 clearTimeout(timeoutId);
33 if (controller.signal.aborted) {
34 return createTryError('TimeoutError', `Operation timed out after ${timeoutMs}ms`);
35 }
36 throw error;
37 }
38}
39
40// Progressive timeout
41async function progressiveTimeout<T>(
42 operation: () => Promise<T>,
43 timeouts: number[]
44): Promise<TryResult<T, TryError>> {
45 for (const timeoutMs of timeouts) {
46 const result = await timeout(operation, timeoutMs);
47 if (isTrySuccess(result)) {
48 return result;
49 }
50
51 // If it's not a timeout error, don't retry
52 if (!hasErrorType(result, 'TimeoutError')) {
53 return result;
54 }
55 }
56
57 return createTryError(
58 'TimeoutError',
59 `Operation failed after all timeout attempts: ${timeouts.join(', ')}ms`
60 );
61}
Best Practices
✅ Do
- • Use type guards for proper type narrowing
- • Combine results when you need all operations to succeed
- • Use mapResult for transforming successful values
- • Use flatMapResult for chaining dependent operations
- • Implement retry logic for transient failures
- • Add timeouts to prevent hanging operations
❌ Don't
- • Ignore type guards and manually check error properties
- • Use unwrapOr with expensive default value computations
- • Retry operations that will never succeed
- • Set overly aggressive timeouts for normal operations
- • Chain too many operations without error handling
- • Use combinators when simple sequential logic is clearer