Skip to main content

Overview

The Result type has four primary ways to create instances:
  • Result.ok() for successful values
  • Result.err() for error values
  • Result.try() for wrapping sync functions that may throw
  • Result.tryPromise() for wrapping async functions with retry support

Creating Success Results

Result.ok()

Creates an Ok instance wrapping a successful value.
const success = Result.ok(42);
// Ok<number, never>

const withObject = Result.ok({ id: 1, name: "Alice" });
// Ok<{ id: number, name: string }, never>

Creating Ok<void>

For side-effectful operations that don’t return a meaningful value, call Result.ok() without arguments:
const saveUser = (user: User): Result<void, SaveError> => {
  // ... save logic ...
  return Result.ok(); // Ok<void, SaveError>
};

const result = saveUser(user);
if (result.isOk()) {
  console.log("Saved successfully");
}
Result.ok() creates an Ok<void, never> which is compatible with Result<void, E> for any error type E.

Type Signature

function ok(): Ok<void, never>;
function ok<A, E = never>(value: A): Ok<A, E>;

Creating Error Results

Result.err()

Creates an Err instance wrapping an error value.
const failure = Result.err("Something went wrong");
// Err<never, string>

const withError = Result.err(new Error("Network timeout"));
// Err<never, Error>

Using TaggedError for Discriminated Errors

For type-safe error handling, use TaggedError to create discriminated error classes:
class NotFoundError extends TaggedError("NotFoundError")<{
  id: string;
  message: string;
}>() {}

class ValidationError extends TaggedError("ValidationError")<{
  field: string;
  message: string;
}>() {}

type AppError = NotFoundError | ValidationError;

const fetchUser = (id: string): Result<User, AppError> => {
  if (!id) {
    return Result.err(new ValidationError({ 
      field: "id", 
      message: "ID is required" 
    }));
  }
  // ...
};
See Error Handling for comprehensive TaggedError patterns.

Type Signature

function err<T = never, E = unknown>(error: E): Err<T, E>;

Wrapping Throwing Functions

Result.try()

Executes a synchronous function and wraps the result or exception in a Result.

Basic Usage

const parseJSON = (str: string): Result<unknown, UnhandledException> => {
  return Result.try(() => JSON.parse(str));
};

const result = parseJSON('{"key": "value"}');
// Ok({ key: "value" })

const failed = parseJSON('invalid');
// Err(UnhandledException)

Custom Error Handling

Transform caught exceptions into domain-specific errors using the catch handler:
class ParseError extends TaggedError("ParseError")<{
  input: string;
  message: string;
  cause: unknown;
}>() {}

const parseJSON = (str: string): Result<unknown, ParseError> => {
  return Result.try({
    try: () => JSON.parse(str),
    catch: (cause) => new ParseError({
      input: str,
      message: `Failed to parse JSON: ${str.slice(0, 50)}`,
      cause,
    }),
  });
};
If your catch handler throws, Result.try will throw a Panic. Catch handlers should always return an error value, never throw.

Retry on Failure

let attempts = 0;
const result = Result.try(
  () => {
    attempts++;
    if (attempts < 3) throw new Error("fail");
    return "success";
  },
  { retry: { times: 3 } }
);
// Ok("success") after 3 attempts

Type Signature

function try<A>(
  thunk: () => A,
  config?: { retry?: { times: number } }
): Result<A, UnhandledException>;

function try<A, E>(
  options: { 
    try: () => A; 
    catch: (cause: unknown) => E 
  },
  config?: { retry?: { times: number } }
): Result<A, E>;

Wrapping Async Functions

Result.tryPromise()

Executes an async function and wraps the result or rejection in a Result, with advanced retry support.

Basic Usage

const fetchUser = async (id: string) => {
  const result = await Result.tryPromise(() => 
    fetch(`/api/users/${id}`).then(r => r.json())
  );
  
  return result; // Result<User, UnhandledException>
};

Custom Error Handling

class ApiError extends TaggedError("ApiError")<{
  status: number;
  message: string;
  cause: unknown;
}>() {}

const fetchUser = async (id: string): Promise<Result<User, ApiError>> => {
  return Result.tryPromise({
    try: async () => {
      const response = await fetch(`/api/users/${id}`);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      return response.json();
    },
    catch: (cause) => new ApiError({
      status: cause instanceof Error ? 500 : 0,
      message: cause instanceof Error ? cause.message : String(cause),
      cause,
    }),
  });
};

Retry with Exponential Backoff

const fetchWithRetry = async (url: string) => {
  return Result.tryPromise(
    () => fetch(url).then(r => r.json()),
    {
      retry: {
        times: 3,
        delayMs: 100,
        backoff: "exponential", // 100ms, 200ms, 400ms
      },
    }
  );
};

Conditional Retry

Use shouldRetry to retry only specific errors:
class RetryableError extends TaggedError("RetryableError")<{
  message: string;
  cause: unknown;
}>() {}

class FatalError extends TaggedError("FatalError")<{
  message: string;
  cause: unknown;
}>() {}

type ApiError = RetryableError | FatalError;

const result = await Result.tryPromise(
  {
    try: () => callApi(url),
    catch: (e) => {
      if (e instanceof TypeError) {
        return new RetryableError({ message: "Network error", cause: e });
      }
      return new FatalError({ message: "Fatal error", cause: e });
    },
  },
  {
    retry: {
      times: 3,
      delayMs: 100,
      backoff: "exponential",
      shouldRetry: (e) => e._tag === "RetryableError",
    },
  }
);
The shouldRetry predicate receives the error returned by your catch handler, allowing you to make retry decisions based on enriched error context.

Backoff Strategies

{
  retry: {
    times: 3,
    delayMs: 100,
    backoff: "constant", // 100ms, 100ms, 100ms
  }
}

Type Signature

function tryPromise<A>(
  thunk: () => Promise<A>,
  config?: RetryConfig<UnhandledException>
): Promise<Result<A, UnhandledException>>;

function tryPromise<A, E>(
  options: { 
    try: () => Promise<A>; 
    catch: (cause: unknown) => E | Promise<E> 
  },
  config?: RetryConfig<E>
): Promise<Result<A, E>>;

type RetryConfig<E> = {
  retry?: {
    times: number;
    delayMs: number;
    backoff: "linear" | "constant" | "exponential";
    shouldRetry?: (error: E) => boolean;
  };
};

When to Use Each Method

Use when you already have a success value and want to wrap it in a Result.
const validated = validateInput(data);
return Result.ok(validated);

Next Steps

Transforming Results

Learn how to transform success and error values with map, mapError, and andThen

Error Handling

Master TaggedError, exhaustive matching, and error recovery patterns

Build docs developers (and LLMs) love