Skip to main content

@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]
fn
Function
required
The function to execute
args
...P
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]>
fn
Function
required
The async function or promise-returning function to execute
args
...P
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
tryFunction
() => TTryResult
required
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
tryFunction
() => TTryResult
required
The function to execute in the try block
finallyFunction
() => void
required
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
tryFunction
() => TTryResult
required
The function to execute in the try block
catchFunction
(ex: unknown) => TCatchResult
required
Function to handle errors
finallyFunction
() => void
required
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

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 };
}

Patterns and Best Practices

1

Destructure Results Consistently

Always destructure the tuple for clarity:
const [error, result] = tryCatch(() => operation());
2

Check Errors First

Handle errors before accessing results:
const [error, data] = await tryCatchPromise(fetchData);
if (error) {
  return handleError(error);
}
// Safe to use data here
3

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;
4

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

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.

Build docs developers (and LLMs) love