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"
}));
}
// ...
};
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
Result.ok()
Result.err()
Result.try()
Result.tryPromise()
Use when you already have a success value and want to wrap it in a Result.const validated = validateInput(data);
return Result.ok(validated);
Use when you’ve detected an error condition and want to return it.if (!user) {
return Result.err(new NotFoundError({ id, message: "User not found" }));
}
Use to wrap third-party sync functions that throw exceptions.const parsed = Result.try(() => JSON.parse(input));
Use to wrap async operations with automatic error handling and retry logic.const data = await Result.tryPromise(() => fetch(url));
Next Steps