Skip to content

austin-weeks/errgo-ts

Repository files navigation

Package Version Bundle Size GitHub License Package Downloads Code Coverage Tests

Forgo your error woes with errgo's ergonomic error handling!

errgo-ts

A lightweight TypeScript library for ergonomic error handling, inspired by Go and Rust.

Offers error handling utilities, a Result type, and an equivalent to the defer keyword from Go.

Installation

Add the library as a dependency with your preferred package manager:

pnpm add errgo-ts

npm install errgo-ts

bun add errgo-ts

Why would I use this?

You're fed up with JavaScript's error handling patterns and have written this too many times to count:

let data;
//    ^ `data` must be declared outside the try/catch - but now it's mutable!
try {
  data = somethingThatCouldFail();
} catch (e: unknown) {
  //     ^ we have no way of knowing the type of `e`
  console.error("Couldn't do it:", e);
}
doSomethingWithData(data);

Why wouldn't I use this?

You want to entirely avoid try/catch blocks and native JavaScript error handling. If that's your goal, try something like neverthrow.

Your application needs maximum performance. There is a small amount of overhead with most of errgo-ts's functions, namely the allocation of closures. That said, it's JavaScript. If you're that concerned about performance, you should probably be using a different language.

API

Error Handling Utilities

safeTry - Errors-as-values try/catch wrapper

Execute a function safely and operate on the result instead of throwing and catching errors. The same helper works seamlessly with both sync and async functions.

Sync Usage:

import { safeTry } from "errgo-ts";

const res = safeTry(() => thisMightThrow());
if (res.err) {
  console.error("It failed:", res.err);
  return;
}
doSomethingElse(res.val);

Async Usage:

import { safeTry } from "errgo-ts";

const usersRes = await safeTry(() => fetch("/api/users").then((r) => r.json()));
if (usersRes.err) {
  displayErrorMsg("Failed to fetch users");
  return;
}
doSomethingWithUsers(usersRes.val);

coerceError - No more unknown catches

Converts an object of an unknown type to an Error instance. Handles all the weird ways JavaScript allows throwing non-error objects.

import { coerceError } from "errgo-ts";

try {
  throw "i'm throwing a string! (for some reason)";
} catch (e: unknown) {
  const error = coerceError(e);
  console.assert(error instanceof Error);
}

safeTry, scope variations, and propagateError all use this function under the hood!

propagateError - Declarative error propagation

Add context to errors without verbose try/catch blocks while preserving the original cause chain.

Instead of this verbose pattern...

let data;
try {
  data = getData();
} catch (e) {
  throw new Error("Failed to get data", { cause: e });
}

...use propagateError!

import { propagateError } from "errgo-ts";

const data = propagateError("Failed to get data", () => getData());

Result Type

A type representing either success or failure. Result objects can contain val or err, but never both.

type Result<T, E = Error> =
  | { val: T; err?: undefined }
  | { err: E; val?: undefined };
import { type Result } from "errgo-ts";

const success: Result<number> = { val: 2 };
const failure: Result<number, MyCustomError> = { err: new MyCustomError() };

errgo-ts's Result is a little bit like Go's (T, error) tuple, and a little bit like Rust's Result enum, but also not really quite either.

scope - Execute functions with deferred actions

scope introduces an equivalent to Go's defer keyword. This allows you to defer code execution until the completion of the scope.

import { scope } from "errgo-ts";

scope.safe((defer) => {
  const conn = new Connection();
  defer(() => conn.close());

  conn.send("Hello!");
});

scope provides three variations for flexible error handling:

scope.safe - Returns a Result object

Returns a Result object. Never throws. In most cases, you should use this variation.

import { scope } from "errgo-ts";

const res = scope.safe((defer) => {
  console.log("Start");
  defer(() => console.log("Cleanup"));
  console.log("Doing work...");
  return "OK";
});
if (!res.err) {
  console.log("Result:", res.val);
}

Output:

Start
Doing work...
Cleanup
Result: OK

scope.throwing - Re-throws errors

Returns the executed function's value and re-throws any errors.

try {
  const data = scope.throwing((defer) => {
    console.log("Start");
    defer(() => console.log("Cleanup"));
    console.log("Doing work...");
    throw new Error("uh oh!");
  });
  console.log("Data:", data);
} catch (e) {
  console.error("Caught:", e);
}

Output:

Start
Doing work...
Cleanup
Caught: uh oh!

scope.handled - Calls a provided error handler

Executes a callback on error. Allows for declarative error handling when the scope returns no value.

scope.handled(
  (err) => console.error("Error in scope:", err),
  (defer) => {
    console.log("Start");
    defer(() => console.log("Cleanup"));
    console.log("Doing work...");
    throw new Error("uh oh!");
  }
);

Output:

Start
Doing work...
Cleanup
Error in scope: uh oh!

About

Ergonomic TypeScript error handling

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Contributors