Skip to content

vanyauhalin/result

Repository files navigation

result

Safe error handling without exceptions for JavaScript, inspired by Go's error model.

Contents

Why?

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.

Installation

npm

npm install @vanyauhalin/result

GitHub Packages

npm install --registry https://npm.pkg.github.com @vanyauhalin/result

JSR (JavaScript Registry)

npx jsr add @vanyauhalin/result

GitHub Releases

npm install vanyauhalin-result-x.x.x.tgz

Usage

import * 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()

API

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

Result type representing either a success or an error (TypeScript type).

This is a discriminated union of Ok<V, E> and Err<V, E>.

Type parameters
  • V (default: unknown) — type of the success value
  • E (default: Error) — type of the error, must extend Error

Ok

Success variant of Result containing a value (TypeScript type).

Type parameters
  • V — type of the success value
  • E — type of the error, must extend Error
Fields
  • v (V) — the success value
  • err (undefined) — always undefined for success results

Err

Error variant of Result containing an error and optionally a value (TypeScript type).

Type parameters
  • V — type of the success value
  • E — type of the error, must extend Error
Fields
  • v (V | undefined) — the optional value
  • err (E) — the error

ok(v)

Creates a success Result containing the given value.

Parameters
  • v (V) — the value to wrap in a success result
Returns

Success result containing the value (Ok<V, E>).

err(v, err)

Creates an error Result containing the given error, and optionally a value.

Parameters
  • v (V) — the optional value to include with the error
  • err (E extends Error) — the error to wrap in an error result
Returns

Error result containing the error (Err<V, E>).

must(r)

Unwraps a Result, throwing the error if it exists.

Parameters
Throws

The error contained in the result, if any.

Returns

The value contained in the result (V).

Example
let s = "https://example.com"
let r = safeNew(URL, s)
let v = must(r)
// v is a URL, or error is thrown

safeNew(fn, ...args)

Safely calls a constructor function, returning a Result.

If the thrown value is not an Error, it will be wrapped in a NonError.

Parameters
  • fn (new (...args: A) => R) — the constructor function to call
  • args (...A) — the arguments to pass to the constructor function
Returns

A result containing the constructed object or an error (Result<R>).

Example
let s = "https://example.com"
let r = safeNew(URL, s)
if (r.err) {
	// r.err is an Error
} else {
	// r.v is a URL
}

safeSync(fn, ...args)

Safely calls a synchronous function, returning a Result.

If the thrown value is not an Error, it will be wrapped in a NonError.

Parameters
  • fn ((...args: A) => R) — the synchronous function to call
  • args (...A) — the arguments to pass to the function
Returns

A result containing the return value or an error (Result<R>).

Example
let s = "{}"
let r = safeSync(JSON.parse, s)
if (r.err) {
	// r.err is an Error
} else {
	// r.v is any
}

safeAsync(fn, ...args)

Safely calls an asynchronous function, returning a Result.

If the thrown value is not an Error, it will be wrapped in a NonError.

Parameters
  • fn ((...args: A) => PromiseLike<R>) — the asynchronous function to call
  • args (...A) — the arguments to pass to the function
Returns

Promise that resolves to a result containing the awaited value or an error (Promise<Result<Awaited<R>>>).

Example
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
}

NonError

Error class representing a non-Error being thrown.

Fields
  • name ("NonError") — the error name
  • cause (unknown) — the original thrown value that was not an Error
Constructor

NonErrorOptions

Options for NonError (TypeScript type).

Extends the standard ErrorOptions with an explicit cause field.

Fields
  • cause (unknown) — the original thrown value that was not an Error

ESLint plugin

See @vanyauhalin/eslint-plugin-result for accompanying ESLint plugin.

Compatibility

This package is ESM only. The minimum supported Node.js version is 16.

License

Code: MIT © Ivan Uhalin
Illustrations: CC BY-NC-SA 4.0 © Ivan Uhalin

About

A Result type, inspired by Go's error model

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors