Skip to content

Instantly share code, notes, and snippets.

@danawoodman
Last active February 26, 2026 17:15
Show Gist options
  • Select an option

  • Save danawoodman/7b2268c12fb69ad85b664fc84ce866b4 to your computer and use it in GitHub Desktop.

Select an option

Save danawoodman/7b2268c12fb69ad85b664fc84ce866b4 to your computer and use it in GitHub Desktop.
Rust-like "results" for TypeScript projects

Rust-like "results" for TypeScript projects

Handling errors in JavaScript/TypeScript kinda sucks. throwing breaks the control flow, is not "discoverable" (e.g. impossible to really know what will happen in error states), and it is generally more tricky to get well typed error responses.

Taking inspiration from Rust's Result type, we can implement something similar in TypeScript with a minimal amount of code and no dependencies. It's not as robust as Rust's of course, but it has been very useful for me on a variety of large projects.

Implementation

Save the following TypeScript in your project and import it to use the various result utils:

export interface Ok<T> {
	ok: true;
	value: T;
}

export interface Err<E> {
	ok: false;
	value: E;
}

export type Result<T, E = Error> = Ok<T> | Err<E>;
export type AsyncResult<T, E = Error> = Promise<Result<T, E>>;

/**
 * Create a success result.
 */
export function Ok<T>(value: T): Ok<T> {
	const result: Ok<T> = { ok: true, value };
	return result;
}

/**
 * Create an error result.
 */
export function Err<E>(value: E): Err<E> {
	const result: Err<E> = { ok: false, value };
	return result;
}

/**
 * A lightweight, synchronous wrapper around a function that returns a value or throws an error.
 */
export function Try<T, E = Error>(
	fn: () => T,
	fallback?: (error: unknown) => E,
): Result<T, E> {
	try {
		const result = fn();
		return Ok(result);
	} catch (error) {
		return Err(fallback ? fallback(error) : (error as E));
	}
}

/**
 * A lightweight, asynchronous wrapper around a function that returns a promise or throws an error.
 */
export async function TryAsync<T, E = Error>(
	fn: () => Promise<T> | T,
	fallback?: (error: unknown) => E,
): Promise<Result<T, E>> {
	try {
		const result = await fn();
		return Ok(result);
	} catch (error) {
		return Err(fallback ? fallback(error) : (error as E));
	}
}

Usage

Try<Value, Error>() or TryAsync<Value, Error>()

Try is useful if you want automatically catching of thrown errors (auto-wrapped with Err(error)) and simple handling of return values (you just have to return a raw value which will get auto-wrapped with an Ok(value) response.

This is probably what you'll want to use in most cases.

import { Try } from "./results";
import { someAsyncFuncThatCouldThrow } from "./some-async-func-that-could-throw";

// Use Try to wrap code and automatically return a `Result` type
function handleUnexpectedFailures() {
  return TryAsync<string>(async () => {
    const result = await someAsyncFuncThatCouldThrow();
    return `here is the result - ${result}`;
  });
}

const result = await handleUnexpectedFailures();

if (!result.ok) {
  // Failures will return `false` for `result.ok`:
  console.error(
    "failed!",
    result.value // result.value will be an instance of Error
  );
} else {
  // if success, result.ok will be true and the value will be the result of the function
  console.log("value:", result.value);
}

You can use Try() with sync functions or TryAsync() with async functions.

Ok / Err / Result

If you want to manually return results you can use the Ok and Err utils. This is useful in cases where you want typed error responses or where you don't need to handle code that might throw:

import { Ok, Err, type Result } from "./results";

// Use Try to wrap code and automatically return a `Result` type.
// Optionally type the response using Result<T, E>
function manuallyReturnResults(): Result<string, string> {
  if (Math.random() > 0.5) return Err("Failed");
  return Ok("Success");
}

const res1 = manuallyReturnResults();

if (!res1.ok) {
  console.error("failed!", res1.value);
} else {
  console.log("value:", res1.value);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment