Evercatch - v0.10.0
    Preparing search index...

    Evercatch

    Version Downloads Minzipped size

    No more uncaught errors!

    Evercatch is a tiny, dependency-free TypeScript library that turns thrown errors into values. Errors become part of a function's return type, so the compiler tells you where they are and refuses to let you read a value you haven't checked for yet.

    npm install evercatch
    # or
    yarn add evercatch
    # or
    pnpm add evercatch

    Everything is built on one type: a readonly tuple of [error, value].

    type Result<T, E> = readonly [null, T] | readonly [E, null];
    

    Destructure it, check the error, and TypeScript narrows the value for you:

    import { err, ok, type Result } from "evercatch";

    function divide(a: number, b: number): Result<number, Error> {
    if (b === 0) {
    return err(new Error("Division by zero"));
    }
    return ok(a / b);
    }

    const [error, value] = divide(10, 2);

    if (error) {
    console.error(error.message);
    } else {
    console.log(value); // number — narrowed, not number | null
    }

    null in the error slot means "this result is ok", so an error can never be nullish. Every error type in the library is constrained to reject null and undefined at compile time.

    Wrap a call that might throw and get a result back instead.

    import { fromPromise, resultFrom } from "evercatch";

    const [parseError, config] = resultFrom(() => JSON.parse(raw));

    const [fetchError, response] = await fromPromise(
    fetch("https://api.example.com/data"),
    );

    Or wrap the function once and reuse the safe version:

    import { fromAsyncThrowable, fromThrowable } from "evercatch";

    const safeParse = fromThrowable(JSON.parse);
    const safeFetch = fromAsyncThrowable(fetch);

    const [error, data] = safeParse(raw);
    const [fetchError, response] = await safeFetch("https://api.example.com/data");

    Anything thrown that isn't an Error is wrapped in one, with the original value kept as cause.

    Every catching function takes an optional mapErr to turn the caught value into an error type of your choosing — a string union, a tagged object, your own error class. Whatever you return becomes the error type of the result.

    import { fromPromise } from "evercatch";

    type FetchError = "NETWORK_ERROR" | "TIMEOUT";

    const [error, response] = await fromPromise(
    fetch("https://api.example.com/data"),
    (e): FetchError => (e instanceof DOMException ? "TIMEOUT" : "NETWORK_ERROR"),
    );

    if (error === "TIMEOUT") {
    // ...
    }

    When you'd rather not handle the error at the call site, unwrap the result with a fallback — or throw after all.

    import { unwrapOr, unwrapOrElse, unwrapOrThrow } from "evercatch";

    unwrapOr(divide(10, 0), 0); // 0
    unwrapOrElse(divide(10, 0), (error) => error.message.length); // computed
    unwrapOrThrow(divide(10, 0)); // throws the error

    The async variants take a Promise of a result and return a promise: unwrapAsyncOr, unwrapAsyncOrElse and unwrapAsyncOrThrow.

    Results compose by returning early. Errors travel upward as values, so a function that can fail has a signature that says so.

    import { err, fromPromise, ok, type ResultAsync } from "evercatch";
    import { auth } from "./auth";

    async function fetchUserData(): ResultAsync<UserData, Error> {
    const [authError, user] = await fromPromise(auth());
    if (authError) {
    return err(authError);
    }

    const [fetchError, response] = await fromPromise(
    fetch(`https://api.example.com/user/${user.id}`),
    );
    if (fetchError) {
    return err(fetchError);
    }
    if (!response.ok) {
    return err(new Error("Failed to fetch user data"));
    }

    return await fromPromise(response.json());
    }

    The same functions are also grouped under the type they work with, which makes for shorter names at the call site. This is purely a matter of preference — the namespace members and the standalone exports are the same functions.

    import { Result, ResultAsync, ResultAsyncFn, ResultFn } from "evercatch";

    Result.ok(42);
    Result.from(() => JSON.parse(raw));
    Result.unwrapOr(someResult, 0);

    await ResultAsync.from(fetch(url));

    const safeParse = ResultFn.from(JSON.parse);
    const safeFetch = ResultAsyncFn.from(fetch);

    Note that Result, ResultAsync, ResultFn and ResultAsyncFn are each both a type and a value, so a single import gives you both.

    Full API reference: fransek.github.io/evercatch

    MIT