@kreisler/try-catch
A collection of functional utilities for elegant error handling without nested try-catch blocks. Simplify your error handling with clean, functional patterns.
Installation
npm install @kreisler/try-catch
Overview
Traditional try-catch blocks can make code harder to read and lead to nested structures. This package provides functional wrappers that return results in a predictable format, making error handling cleaner and more composable.
All functions support both synchronous and asynchronous operations with full TypeScript support.
Exports
The package exports four main functions:
tryCatch - Basic try-catch wrapper returning [error, result] tuple
tryCatchPromise - Async version of tryCatch for promises
tryToCatch - Try-catch with error transformation callback
tryFinally - Try-finally wrapper for cleanup operations
tryCatchFinally - Complete try-catch-finally wrapper
API Reference
tryCatch
Synchronous try-catch wrapper that returns a tuple.
function tryCatch < D , P extends any []>(
fn : ( ... args : P ) => D ,
... args : P
) : [ null , D ] | [ Error ]
Arguments to pass to the function
Returns: [null, result] on success or [Error] on failure
Example
import { tryCatch } from '@kreisler/try-catch' ;
// Success case
const [ error , result ] = tryCatch (() => {
return 'Success!' ;
});
if ( error ) {
console . error ( error . message );
} else {
console . log ( result ); // 'Success!'
}
// Error case
const [ err , data ] = tryCatch (() => {
throw new Error ( 'Something went wrong' );
});
if ( err ) {
console . error ( err . message ); // 'Something went wrong'
}
tryCatchPromise
Asynchronous try-catch wrapper for promises and async functions.
async function tryCatchPromise < D , P extends any []>(
fn : ( ... args : P ) => Promise < D > | D ,
... args : P
) : Promise <[ null , D ] | [ Error ]>
The async function or promise-returning function to execute
Arguments to pass to the function
Returns: Promise<[null, result] | [Error]>
Example
import { tryCatchPromise } from '@kreisler/try-catch' ;
// Fetch example
const [ error , response ] = await tryCatchPromise (
fetch ,
'https://jsonplaceholder.typicode.com/todos/1'
);
if ( error ) {
console . error ( 'Fetch failed:' , error . message );
} else {
const data = await response . json ();
console . log ( data );
}
// Async function example
const [ err , user ] = await tryCatchPromise ( async ( id ) => {
const res = await fetch ( `/api/users/ ${ id } ` );
return res . json ();
}, 123 );
if ( err ) {
console . error ( 'Failed to fetch user:' , err );
} else {
console . log ( 'User:' , user );
}
tryToCatch
Try-catch with error transformation callback.
function tryToCatch < TTryResult , TCatchResult >(
tryFunction : () => TTryResult ,
catchFunction : ( ex : unknown ) => TCatchResult
) : TTryResult | TCatchResult
The function to execute in the try block
catchFunction
(ex: unknown) => TCatchResult
required
Function to handle errors and return a fallback value
Returns: Either the successful result or the catch function’s result
Example
import { tryToCatch } from '@kreisler/try-catch' ;
// Return default value on error
const result = tryToCatch (
() => {
return JSON . parse ( '{invalid json}' );
},
( error ) => {
console . error ( 'Parse error:' , error );
return {}; // Return default empty object
}
);
console . log ( result ); // {}
// Re-throw with additional context
const data = tryToCatch (
() => {
return riskyOperation ();
},
( error ) => {
throw new Error ( `Operation failed: ${ error . message } ` );
}
);
tryFinally
Try-finally wrapper for cleanup operations.
function tryFinally < TTryResult >(
tryFunction : () => TTryResult ,
finallyFunction : () => void
) : TTryResult | undefined
The function to execute in the try block
Cleanup function that always executes
Returns: The result of tryFunction, or undefined if an error occurred
Example
import { tryFinally } from '@kreisler/try-catch' ;
let resource = null ;
const result = tryFinally (
() => {
resource = acquireResource ();
return processResource ( resource );
},
() => {
// Cleanup always runs
if ( resource ) {
releaseResource ( resource );
}
}
);
tryCatchFinally
Complete try-catch-finally wrapper.
function tryCatchFinally < TTryResult , TCatchResult >(
tryFunction : () => TTryResult ,
catchFunction : ( ex : unknown ) => TCatchResult ,
finallyFunction : () => void
) : TTryResult | TCatchResult
The function to execute in the try block
catchFunction
(ex: unknown) => TCatchResult
required
Function to handle errors
Cleanup function that always executes
Returns: Either the successful result or the catch function’s result
Example
import { tryCatchFinally } from '@kreisler/try-catch' ;
let connection = null ;
const result = tryCatchFinally (
() => {
connection = database . connect ();
return connection . query ( 'SELECT * FROM users' );
},
( error ) => {
console . error ( 'Query failed:' , error );
return []; // Return empty array on error
},
() => {
// Always cleanup
if ( connection ) {
connection . close ();
}
}
);
Common Use Cases
API Calls
JSON Parsing
File Operations
Form Validation
import { tryCatchPromise } from '@kreisler/try-catch' ;
async function fetchUser ( id : number ) {
const [ error , response ] = await tryCatchPromise (
fetch ,
`/api/users/ ${ id } `
);
if ( error ) {
return { error: 'Failed to fetch user' };
}
const [ parseError , user ] = await tryCatchPromise (() =>
response . json ()
);
if ( parseError ) {
return { error: 'Failed to parse response' };
}
return { user };
}
import { tryToCatch } from '@kreisler/try-catch' ;
function safeJSONParse ( text : string , defaultValue = null ) {
return tryToCatch (
() => JSON . parse ( text ),
( error ) => {
console . warn ( 'Invalid JSON:' , error );
return defaultValue ;
}
);
}
const data = safeJSONParse ( userInput , {});
import { tryCatchFinally } from '@kreisler/try-catch' ;
import { open } from 'fs/promises' ;
async function readConfig ( path : string ) {
let fileHandle = null ;
return await tryCatchFinally (
async () => {
fileHandle = await open ( path , 'r' );
const content = await fileHandle . readFile ( 'utf-8' );
return JSON . parse ( content );
},
( error ) => {
console . error ( 'Failed to read config:' , error );
return {}; // Default config
},
async () => {
if ( fileHandle ) {
await fileHandle . close ();
}
}
);
}
import { tryCatch } from '@kreisler/try-catch' ;
function validateEmail ( email : string ) : boolean {
const [ error ] = tryCatch (() => {
if ( ! email . includes ( '@' )) {
throw new Error ( 'Invalid email' );
}
// More validation...
});
return ! error ;
}
Patterns and Best Practices
Destructure Results Consistently
Always destructure the tuple for clarity: const [ error , result ] = tryCatch (() => operation ());
Check Errors First
Handle errors before accessing results: const [ error , data ] = await tryCatchPromise ( fetchData );
if ( error ) {
return handleError ( error );
}
// Safe to use data here
Chain Multiple Operations
Combine functions for sequential error handling: const [ err1 , data1 ] = await tryCatchPromise ( fetchUser , id );
if ( err1 ) return ;
const [ err2 , data2 ] = await tryCatchPromise ( fetchPosts , data1 . userId );
if ( err2 ) return ;
Use Type Guards
TypeScript will narrow types after error checks: const [ error , result ] = tryCatch < User >(() => getUser ());
if ( error ) {
// error is Error
console . error ( error . message );
return ;
}
// result is User (not undefined)
console . log ( result . name );
Comparison with Traditional Try-Catch
Traditional
With @kreisler/try-catch
try {
const response = await fetch ( '/api/data' );
try {
const data = await response . json ();
try {
const processed = processData ( data );
return processed ;
} catch ( processError ) {
console . error ( 'Process error:' , processError );
return null ;
}
} catch ( parseError ) {
console . error ( 'Parse error:' , parseError );
return null ;
}
} catch ( fetchError ) {
console . error ( 'Fetch error:' , fetchError );
return null ;
}
The functional approach eliminates nesting and makes the error handling flow more explicit and linear.
Advanced Examples
Multiple API Calls with Error Handling
import { tryCatchPromise } from '@kreisler/try-catch' ;
async function getUserWithPosts ( userId : number ) {
const [ userError , userResponse ] = await tryCatchPromise (
fetch ,
`/api/users/ ${ userId } `
);
if ( userError ) {
return { error: 'Failed to fetch user' , user: null , posts: [] };
}
const [ parseError , user ] = await tryCatchPromise (() => userResponse . json ());
if ( parseError ) {
return { error: 'Failed to parse user' , user: null , posts: [] };
}
const [ postsError , postsResponse ] = await tryCatchPromise (
fetch ,
`/api/users/ ${ userId } /posts`
);
if ( postsError ) {
return { error: null , user , posts: [] }; // User succeeded, posts failed
}
const [ postsParseError , posts ] = await tryCatchPromise (() =>
postsResponse . json ()
);
return {
error: postsParseError ? 'Failed to parse posts' : null ,
user ,
posts: postsParseError ? [] : posts
};
}
Custom Error Types
import { tryToCatch } from '@kreisler/try-catch' ;
class ValidationError extends Error {
constructor ( message : string ) {
super ( message );
this . name = 'ValidationError' ;
}
}
function validateAndProcess ( data : unknown ) {
return tryToCatch (
() => {
if ( ! data ) {
throw new ValidationError ( 'Data is required' );
}
return processData ( data );
},
( error ) => {
if ( error instanceof ValidationError ) {
console . error ( 'Validation failed:' , error . message );
return null ;
}
throw error ; // Re-throw unexpected errors
}
);
}
Type Exports
import type {
Tfn ,
TtryCatch ,
TtryCatchPromise
} from '@kreisler/try-catch' ;
// Tfn: (...args: any[]) => any
// TtryCatch: [error: Error | null | unknown, result?: any]
// TtryCatchPromise: Promise<TtryCatch>
License
MIT License - see the LICENSE file for details.
Links