Safe error handling without exceptions for JavaScript, inspired by Go's error model.
I began my career as a JavaScript developer, but eventually started exploring beyond the JavaScript ecosystem. Go was one of those discoveries that changed how I approach programming — especially error handling.
Go's error handling is polarizing. You either love it or hate it; there is rarely middle ground. I found myself in the first camp. The explicit nature of error handling, where errors are values that must be consciously dealt with, felt refreshingly honest compared to the unpredictable world of exceptions.
This package is my attempt to bring that same clarity and explicitness to JavaScript. Instead of hoping exceptions do not slip through the cracks, you handle errors as intentionally as you handle success cases.
It is purposefully small — perhaps ridiculously so for something published to a registry. For a while, I copied the implementation from project to project. But after some time, I decided to publish it for easier reuse, and so I could write an ESLint plugin to address specific patterns and potential issues.
If you find yourself interested in replacing try-catch with something else, but this package lacks features you need, take a look at NeverThrow. Unlike this implementation, NeverThrow is inspired by Rust's error model and offers way more features.
npm install @vanyauhalin/resultnpm install --registry https://npm.pkg.github.com @vanyauhalin/resultnpx jsr add @vanyauhalin/resultnpm install vanyauhalin-result-x.x.x.tgzimport * as r from "@vanyauhalin/result"
type User = {
login: string
id: number
name: string
}
async function fetchUser(id: string): Promise<r.Result<User>> {
let u = r.safeNew(URL, id, "https://api.github.com/users/")
if (u.err) {
return r.err(new Error(`Creating a URL for user ${id}`, {cause: u.err}))
}
let f = await r.safeAsync(fetch, u.v)
if (f.err) {
return r.err(new Error(`Fetching a user with id ${id}`, {cause: f.err}))
}
let j = await r.safeAsync(f.v.json.bind(f.v))
if (j.err) {
return r.err(new Error(`Getting JSON for user ${id}`, {cause: j.err}))
}
return r.ok(j.v as User)
}
async function main(): Promise<void> {
let u = await fetchUser("ry")
if (u.err) {
console.error(u.err)
return
}
console.log("Login:", u.v.login)
console.log("Name: ", u.v.name)
console.log("ID: ", u.v.id)
// Login: ry
// Name: Ryan Dahl
// ID: 80
}
void main()This package exports core result types Result, Ok,
Err, result utilities ok, err,
must, safe wrapper functions safeNew,
safeSync, safeAsync, and exception
wrapping NonError, NonErrorOptions from the
main module. There is no default export.
Result type representing either a success or an error (TypeScript type).
This is a discriminated union of Ok<V, E> and Err<V, E>.
V(default:unknown) — type of the success valueE(default:Error) — type of the error, must extendError
Success variant of Result containing a value (TypeScript type).
V— type of the success valueE— type of the error, must extendError
v(V) — the success valueerr(undefined) — always undefined for success results
Error variant of Result containing an error and optionally a
value (TypeScript type).
V— type of the success valueE— type of the error, must extendError
v(V | undefined) — the optional valueerr(E) — the error
Creates a success Result containing the given value.
v(V) — the value to wrap in a success result
Success result containing the value (Ok<V, E>).
Creates an error Result containing the given error, and
optionally a value.
v(V) — the optional value to include with the errorerr(E extends Error) — the error to wrap in an error result
Error result containing the error (Err<V, E>).
Unwraps a Result, throwing the error if it exists.
r(Result<V, E>) — the result to unwrap
The error contained in the result, if any.
The value contained in the result (V).
let s = "https://example.com"
let r = safeNew(URL, s)
let v = must(r)
// v is a URL, or error is thrownSafely calls a constructor function, returning a Result.
If the thrown value is not an Error, it will be wrapped in a
NonError.
fn(new (...args: A) => R) — the constructor function to callargs(...A) — the arguments to pass to the constructor function
A result containing the constructed object or an error
(Result<R>).
let s = "https://example.com"
let r = safeNew(URL, s)
if (r.err) {
// r.err is an Error
} else {
// r.v is a URL
}Safely calls a synchronous function, returning a Result.
If the thrown value is not an Error, it will be wrapped in a
NonError.
fn((...args: A) => R) — the synchronous function to callargs(...A) — the arguments to pass to the function
A result containing the return value or an error (Result<R>).
let s = "{}"
let r = safeSync(JSON.parse, s)
if (r.err) {
// r.err is an Error
} else {
// r.v is any
}Safely calls an asynchronous function, returning a Result.
If the thrown value is not an Error, it will be wrapped in a
NonError.
fn((...args: A) => PromiseLike<R>) — the asynchronous function to callargs(...A) — the arguments to pass to the function
Promise that resolves to a result containing the awaited value or an error
(Promise<Result<Awaited<R>>>).
let f = "/tmp/app.log"
let r = await safeAsync(fs.readFile, f, "utf8")
if (r.err) {
// r.err is an Error
} else {
// r.v is a string
}Error class representing a non-Error being thrown.
name("NonError") — the error namecause(unknown) — the original thrown value that was not an Error
o(NonErrorOptions) — configuration for the NonError
Options for NonError (TypeScript type).
Extends the standard ErrorOptions with an explicit cause field.
cause(unknown) — the original thrown value that was not an Error
See @vanyauhalin/eslint-plugin-result for accompanying ESLint
plugin.
This package is ESM only. The minimum supported Node.js version is 16.
Code: MIT © Ivan Uhalin
Illustrations: CC BY-NC-SA 4.0 © Ivan Uhalin
