1.0.1 • Published 7 months ago

@dliv/try-catch v1.0.1

Weekly downloads
-
License
WTFPL
Repository
github
Last release
7 months ago

try-catch

Treat errors as values like Go, Rust, React hooks, etc.

npm version License: WTFPL

import { tryCatch } from '@dliv/try-catch';

// Using array destructuring (Go-style)
async function fetchUserWithArray(id: string) {
    const [user, err] = await tryCatch(fetchUserFromAPI(id));

    if (err) {
        console.error('Failed to fetch user:', err);
        return null;
    }

    return user;
}

// Using object destructuring
async function fetchUserWithObject(id: string) {
    const { data, error } = await tryCatch(fetchUserFromAPI(id));

    if (error) {
        console.error('Failed to fetch user:', error);
        return null;
    }

    return data;
}

Yes: that one method is a mind reader.

Install

npm i @dliv/try-catch

Benefits

  • ✅ Keeps your happy path unindented and pairs well with guard clauses
  • ✅ Dual access pattern (array destructuring or object properties)
  • ✅ Simple: no dependencies, less than 100 LOC, 100% branch coverage, copy-pastable single-file
  • ✅ Type Safety and Runtime Safety - the error is always an instance of Error

Error Handling Comparison

Traditional try/catch

With a traditional try ... catch you have a choice between losing context around what threw or lots of boilerplate wrapping individual calls.

async function fetchUserData(userId) {
    try {
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) {
            throw response;
        }
        return await response.json();
    } catch (error) {
        // what threw? hopefully `error` is detailed (but it might not even be an Error)
        console.error('Error:', error);
        return null;
    }
}

With try-catch

async function fetchUserData(userId) {
    const [response] = await tryCatch(fetch(`/api/users/${userId}`));
    const [json] = await tryCatch(response?.ok ? response.json() : null);
    return json;
}

Or if you need fine grained error handling

async function fetchUserData(userId) {
    const [response, fetchError] = await tryCatch(fetch(`/api/users/${userId}`));
    if (fetchError) {
        console.error('Fetch error:', fetchError);
        return null;
    }

    if (!response.ok) {
        console.error('Response not okay:', response.status);
        return null;
    }

    const [json, jsonError] = await tryCatch(response.json());
    if (jsonError) {
        console.error('JSON parsing error:', jsonError);
        return null;
    }

    return json;
}

Inspiration

  • The errors as values approach in Go and Rust
  • Popular React libraries like this TanStack example: const { status, data, error, isFetching } = usePosts()
  • YouTube videos by AirShip and Theo
  • LoDash's attempt to do this for sync code (comparable to our tryCatchSync)

API

tryCatch<T>(val: Awaitable<T>): Promise<ResultPair<T>>

Accepts a value, Promise, or thenable and returns a Promise containing a result pair / struct.

// With Promise
const [data, error] = await tryCatch(fetch('/api/data'));

// With direct value (automatically wrapped in a resolved Promise)
const [data, error] = await tryCatch(42);

tryCatchSync<T>(val: T | (() => T)): ResultPair<T>

Synchronous version that accepts a value or function and returns a result pair immediately.

// With function that might throw
const [data, error] = tryCatchSync(() => JSON.parse(jsonString));

// With direct value
const [data, error] = tryCatchSync(42);

Return Type: ResultPair<T>

The return value can be accessed in two ways:

  1. Array destructuring: const [data, error] = await tryCatch(...)
  2. Object properties: const { data, error } = await tryCatch(...)

If successful:

  • data contains the returned value
  • error is null

If an error occurs:

  • data is null
  • error contains the Error object (non-Error values are wrapped in an Error with the original as cause)

asError(maybeError: unknown): Error

Utility function that ensures a value is an Error instance. If the value is already an Error, it's returned as-is. Otherwise, it's wrapped in a new Error with the original value as cause.

try {
    // something that might throw
} catch (e) {
    const error = asError(e); // guaranteed to be an Error instance
    console.error(error);
}

License

WTFPL

Less formally: Do what you want. The index.ts is short and dependency free: copy/paste/change.

1.0.1

7 months ago

1.0.0

7 months ago

0.6.0

8 months ago

0.5.0

8 months ago

0.4.0

8 months ago

0.3.0

8 months ago

0.2.0

8 months ago

0.1.0

8 months ago