Forgo your error woes with errgo's ergonomic error handling!
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.
Add the library as a dependency with your preferred package manager:
pnpm add errgo-ts
npm install errgo-ts
bun add errgo-tsYou'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);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.
safeTry- a try/catch replacementResult<T, E>- success or failure typecoerceError- type-safe catchespropagateError- declarative error propagationscope- deterministic scopes withdefer
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);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!
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());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 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:
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
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!
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!