Creation
Result.ok()
Creates a successful result. See ok() and err() for full documentation.
Result.ok(42) // Ok<number, never>
Result.ok() // Ok<void, never>
Result.err()
Creates an error result. See ok() and err() for full documentation.
Result.err("failed") // Err<never, string>
Error Handling
Result.try()
Executes a synchronous function and wraps the result or error in a Result.
Signatures
// Without custom error handler
Result.try<A>(
thunk: () => A,
config?: { retry?: { times: number } }
): Result<A, UnhandledException>
// With custom error handler
Result.try<A, E>(
options: {
try: () => A;
catch: (cause: unknown) => E;
},
config?: { retry?: { times: number } }
): Result<A, E>
Parameters
Function to execute (simple form)
Options object (custom error handler form)options.catch
(cause: unknown) => E
required
Custom error handler that transforms the caught error
Returns
result
Result<A, E | UnhandledException>
Ok with the return value if successful, Err with error if it throws
Behavior
- Executes the function in a try-catch block
- Returns
Ok with the result if successful
- Returns
Err with UnhandledException (or custom error) if it throws
- Retries the specified number of times if configured
- Throws:
Panic if the custom catch handler throws
Examples
// Basic usage
const result = Result.try(() => JSON.parse(jsonString));
// Result<any, UnhandledException>
if (result.isOk()) {
console.log(result.value);
} else {
console.error("Parse failed:", result.error.cause);
}
// With custom error handler
class ParseError extends TaggedError<"ParseError"> {
readonly _tag = "ParseError";
constructor(public input: string, public cause: unknown) {
super();
}
}
const result = Result.try({
try: () => JSON.parse(input),
catch: (e) => new ParseError(input, e),
});
// Result<any, ParseError>
// With retry
const result = Result.try(
() => unstableOperation(),
{ retry: { times: 3 } }
);
Result.tryPromise()
Executes an async function and wraps the result or error in a Result, with retry support.
Signatures
// Without custom error handler
Result.tryPromise<A>(
thunk: () => Promise<A>,
config?: RetryConfig<UnhandledException>
): Promise<Result<A, UnhandledException>>
// With custom error handler
Result.tryPromise<A, E>(
options: {
try: () => Promise<A>;
catch: (cause: unknown) => E | Promise<E>;
},
config?: RetryConfig<E>
): Promise<Result<A, E>>
Parameters
Async function to execute (simple form)
Options object (custom error handler form)Async function to execute
options.catch
(cause: unknown) => E | Promise<E>
required
Custom error handler (can be async)
Optional retry configurationRetry settingsBase delay in milliseconds between retries
config.retry.backoff
"constant" | "linear" | "exponential"
required
Backoff strategy for delays
Predicate to determine if an error should trigger a retry. Defaults to always retry.
Returns
result
Promise<Result<A, E | UnhandledException>>
Promise of Ok with the return value if successful, Err with error if it rejects
Behavior
- Awaits the async function in a try-catch block
- Returns
Ok with the result if successful
- Returns
Err with UnhandledException (or custom error) if it rejects
- Supports retry with configurable backoff strategies
- Supports conditional retry via
shouldRetry predicate
- Throws:
Panic if the custom catch handler throws
Examples
// Basic usage
const result = await Result.tryPromise(() => fetch(url));
// Result<Response, UnhandledException>
// With custom error handler
class ApiError extends TaggedError<"ApiError"> {
readonly _tag = "ApiError";
constructor(public url: string, public cause: unknown) {
super();
}
}
const result = await Result.tryPromise({
try: () => fetch(url),
catch: (e) => new ApiError(url, e),
});
// Result<Response, ApiError>
// With exponential backoff retry
const result = await Result.tryPromise(
() => fetch(url),
{
retry: {
times: 3,
delayMs: 100,
backoff: "exponential", // 100ms, 200ms, 400ms
},
}
);
// Conditional retry based on error type
const result = await Result.tryPromise(
{
try: () => apiCall(url),
catch: (e) => e instanceof NetworkError
? new RetryableError(e)
: new FatalError(e),
},
{
retry: {
times: 3,
delayMs: 100,
backoff: "exponential",
shouldRetry: (error) => error._tag === "RetryableError",
},
}
);
// Async error enrichment
const result = await Result.tryPromise(
{
try: () => callApi(url),
catch: async (e) => {
const rateLimited = await redis.get(`ratelimit:${userId}`);
return new ApiError({ cause: e, rateLimited: !!rateLimited });
},
},
{
retry: {
times: 3,
delayMs: 100,
backoff: "exponential",
shouldRetry: (e) => !e.rateLimited,
},
}
);
Result.map()
Transforms the success value. Supports both data-first and data-last (pipeable) forms.
Signatures
// Data-first
Result.map<A, B, E>(
result: Result<A, E>,
fn: (a: A) => B
): Result<B, E>
// Data-last (pipeable)
Result.map<A, B>(
fn: (a: A) => B
): <E>(result: Result<A, E>) => Result<B, E>
Examples
// Data-first
const result = Result.map(Result.ok(2), x => x * 2);
// Ok(4)
// Data-last (pipeable)
import { pipe } from "fp-ts/function";
const result = pipe(
Result.ok(2),
Result.map(x => x * 2),
Result.map(x => x.toString())
);
// Ok("4")
Result.mapError()
Transforms the error value. Supports both data-first and data-last forms.
Signatures
// Data-first
Result.mapError<A, E, E2>(
result: Result<A, E>,
fn: (e: E) => E2
): Result<A, E2>
// Data-last (pipeable)
Result.mapError<E, E2>(
fn: (e: E) => E2
): <A>(result: Result<A, E>) => Result<A, E2>
Examples
// Data-first
const result = Result.mapError(
Result.err("not found"),
msg => new Error(msg)
);
// Err(Error("not found"))
// Data-last (pipeable)
import { pipe } from "fp-ts/function";
const result = pipe(
Result.err("not found"),
Result.mapError(msg => new Error(msg))
);
Result.andThen()
Chains a Result-returning function. Supports both data-first and data-last forms.
Signatures
// Data-first
Result.andThen<A, B, E, E2>(
result: Result<A, E>,
fn: (a: A) => Result<B, E2>
): Result<B, E | E2>
// Data-last (pipeable)
Result.andThen<A, B, E2>(
fn: (a: A) => Result<B, E2>
): <E>(result: Result<A, E>) => Result<B, E | E2>
Examples
// Data-first
const result = Result.andThen(
Result.ok(2),
x => x > 0 ? Result.ok(x) : Result.err("negative")
);
// Ok(2)
// Data-last (pipeable)
import { pipe } from "fp-ts/function";
const result = pipe(
Result.ok(5),
Result.andThen(x => x > 0 ? Result.ok(x) : Result.err("non-positive")),
Result.andThen(x => x < 10 ? Result.ok(x * 2) : Result.err("too large"))
);
// Ok(10)
Result.andThenAsync()
Chains an async Result-returning function. Supports both data-first and data-last forms.
Signatures
// Data-first
Result.andThenAsync<A, B, E, E2>(
result: Result<A, E>,
fn: (a: A) => Promise<Result<B, E2>>
): Promise<Result<B, E | E2>>
// Data-last (pipeable)
Result.andThenAsync<A, B, E2>(
fn: (a: A) => Promise<Result<B, E2>>
): <E>(result: Result<A, E>) => Promise<Result<B, E | E2>>
Examples
// Data-first
const result = await Result.andThenAsync(
Result.ok(userId),
async (id) => {
const user = await fetchUser(id);
return Result.ok(user);
}
);
// Data-last (pipeable)
const result = await pipe(
Result.ok(userId),
Result.andThenAsync(async (id) => {
const user = await db.getUser(id);
return user ? Result.ok(user) : Result.err("not found");
})
);
Result.flatten()
Flattens a nested Result into a single Result.
Result.flatten<T, E, E2>(
result: Result<Result<T, E>, E2>
): Result<T, E | E2>
Parameters
result
Result<Result<T, E>, E2>
required
Nested Result to flatten
Returns
Flattened Result with union of both error types
Examples
const nested: Result<Result<number, string>, Error> =
Result.ok(Result.ok(42));
const flat = Result.flatten(nested);
// Ok(42): Result<number, string | Error>
// With error in outer layer
const nested = Result.err<Result<number, string>, Error>(new Error("outer"));
const flat = Result.flatten(nested);
// Err(Error("outer")): Result<number, string | Error>
// With error in inner layer
const nested = Result.ok(Result.err<number, string>("inner"));
const flat = Result.flatten(nested);
// Err("inner"): Result<number, string>
Pattern Matching
Result.match()
Pattern matches on a Result. Supports both data-first and data-last forms.
Signatures
// Data-first
Result.match<A, E, T>(
result: Result<A, E>,
handlers: {
ok: (a: A) => T;
err: (e: E) => T;
}
): T
// Data-last (pipeable)
Result.match<A, E, T>(
handlers: {
ok: (a: A) => T;
err: (e: E) => T;
}
): (result: Result<A, E>) => T
Examples
// Data-first
const output = Result.match(
Result.ok(42),
{
ok: (n) => `Success: ${n}`,
err: (e) => `Error: ${e}`,
}
);
// "Success: 42"
// Data-last (pipeable)
import { pipe } from "fp-ts/function";
const output = pipe(
getUserResult(),
Result.match({
ok: (user) => ({ status: 200, body: user }),
err: (error) => ({ status: 500, body: { error } }),
})
);
Side Effects
Result.tap()
Runs a side effect on success. Supports both data-first and data-last forms.
Signatures
// Data-first
Result.tap<A, E>(
result: Result<A, E>,
fn: (a: A) => void
): Result<A, E>
// Data-last (pipeable)
Result.tap<A>(
fn: (a: A) => void
): <E>(result: Result<A, E>) => Result<A, E>
Examples
// Data-first
const result = Result.tap(
Result.ok(user),
(u) => console.log("User:", u)
);
// Logs "User: {...}" and returns Ok(user)
// Data-last (pipeable)
import { pipe } from "fp-ts/function";
const result = pipe(
Result.ok(2),
Result.tap(x => console.log("Value:", x)),
Result.map(x => x * 2)
);
Result.tapAsync()
Runs an async side effect on success. Supports both data-first and data-last forms.
Signatures
// Data-first
Result.tapAsync<A, E>(
result: Result<A, E>,
fn: (a: A) => Promise<void>
): Promise<Result<A, E>>
// Data-last (pipeable)
Result.tapAsync<A>(
fn: (a: A) => Promise<void>
): <E>(result: Result<A, E>) => Promise<Result<A, E>>
Examples
// Data-first
const result = await Result.tapAsync(
Result.ok(user),
async (u) => {
await analytics.track("user_loaded", { id: u.id });
}
);
// Data-last (pipeable)
const result = await pipe(
Result.ok(data),
Result.tapAsync(async (d) => {
await logger.info("Data loaded", d);
})
);
Unwrapping
Result.unwrap()
Extracts the value or throws.
Result.unwrap<A, E>(result: Result<A, E>, message?: string): A
Parameters
Optional custom error message
Returns
Examples
const value = Result.unwrap(Result.ok(42));
// 42
Result.unwrap(Result.err("failed"));
// throws Panic
Result.unwrap(Result.err("failed"), "Custom message");
// throws Panic with "Custom message"
Result.unwrapOr()
Extracts the value or returns a fallback. Supports both data-first and data-last forms.
Signatures
// Data-first
Result.unwrapOr<A, E, B>(
result: Result<A, E>,
fallback: B
): A | B
// Data-last (pipeable)
Result.unwrapOr<B>(
fallback: B
): <A, E>(result: Result<A, E>) => A | B
Examples
// Data-first
const value = Result.unwrapOr(Result.ok(42), 0);
// 42
const value = Result.unwrapOr(Result.err("failed"), 0);
// 0
// Data-last (pipeable)
import { pipe } from "fp-ts/function";
const value = pipe(
getUserName(id),
Result.unwrapOr("Anonymous")
);
Generator Composition
Result.gen()
Generator-based composition for Result types using yield* syntax.
Signatures
// Sync generator
Result.gen<Yield extends Err<never, unknown>, R extends AnyResult>(
body: () => Generator<Yield, R, unknown>
): Result<InferOk<R>, InferYieldErr<Yield> | InferErr<R>>
// Sync generator with thisArg
Result.gen<Yield extends Err<never, unknown>, R extends AnyResult, This>(
body: (this: This) => Generator<Yield, R, unknown>,
thisArg: This
): Result<InferOk<R>, InferYieldErr<Yield> | InferErr<R>>
// Async generator
Result.gen<Yield extends Err<never, unknown>, R extends AnyResult>(
body: () => AsyncGenerator<Yield, R, unknown>
): Promise<Result<InferOk<R>, InferYieldErr<Yield> | InferErr<R>>>
// Async generator with thisArg
Result.gen<Yield extends Err<never, unknown>, R extends AnyResult, This>(
body: (this: This) => AsyncGenerator<Yield, R, unknown>,
thisArg: This
): Promise<Result<InferOk<R>, InferYieldErr<Yield> | InferErr<R>>>
Parameters
body
() => Generator | AsyncGenerator
required
Generator function that yields Results and returns a final Result
Optional this context for the generator function
Returns
result
Result<T, E> | Promise<Result<T, E>>
Result with success type from the return value and error type as union of all yielded errors
Behavior
- Use
yield* to unwrap Result values in the generator
- If any yielded Result is Err, the generator short-circuits and returns that Err
- If all yields succeed, the final returned Result is returned
- Error types from all yields form a union automatically
- Supports both sync and async generators
- Throws:
Panic if generator body or cleanup throws
Examples
// Basic composition
const result = Result.gen(function* () {
const a = yield* getA(); // Result<A, ErrorA>
const b = yield* getB(a); // Result<B, ErrorB>
return Result.ok({ a, b });
});
// Result<{a: A, b: B}, ErrorA | ErrorB>
// Short-circuit on first error
const result = Result.gen(function* () {
const a = yield* Result.ok(1);
const b = yield* Result.err("failed"); // Short-circuits here
const c = yield* Result.ok(3); // Never executed
return Result.ok({ a, b, c });
});
// Err("failed")
// Normalize error types with mapError
const result = Result.gen(function* () {
const a = yield* getA();
const b = yield* getB(a);
return Result.ok({ a, b });
}).mapError(e => new UnifiedError(e._tag, e.message));
// Result<{a, b}, UnifiedError>
// Async generators with Result.await
const result = await Result.gen(async function* () {
const userId = yield* getUserId();
const user = yield* Result.await(fetchUser(userId));
const profile = yield* Result.await(fetchProfile(user.id));
return Result.ok({ user, profile });
});
// With thisArg context
class UserService {
constructor(private db: Database) {}
getFullUser(id: string) {
return Result.gen(function* () {
const user = yield* this.getUser(id);
const profile = yield* this.getProfile(user.id);
return Result.ok({ user, profile });
}, this); // Pass 'this' context
}
}
// Complex workflow
const result = Result.gen(function* () {
// Validate input
const input = yield* validateInput(data);
// Fetch dependencies
const userResult = yield* getUser(input.userId);
const configResult = yield* getConfig(input.configId);
// Transform data
const processed = yield* processData({
input,
user: userResult,
config: configResult,
});
// Return final result
return Result.ok(processed);
});
// All error types automatically unified
Result.await()
Wraps a Promise<Result> to be yieldable in async Result.gen() blocks.
Result.await<T, E>(
promise: Promise<Result<T, E>>
): AsyncGenerator<Err<never, E>, T, unknown>
Parameters
promise
Promise<Result<T, E>>
required
Promise of Result to await and unwrap
Returns
generator
AsyncGenerator<Err<never, E>, T, unknown>
Async generator that yields the error if Err, otherwise returns the value
Behavior
- Awaits the Promise
- If Result is Ok, returns the value
- If Result is Err, yields the error (short-circuits the generator)
- Must be used with
yield* inside Result.gen() async generators
Examples
// Basic async composition
const result = await Result.gen(async function* () {
const user = yield* Result.await(fetchUser(id));
const profile = yield* Result.await(fetchProfile(user.id));
return Result.ok({ user, profile });
});
// Mixing sync and async Results
const result = await Result.gen(async function* () {
// Sync Result - use yield* directly
const validated = yield* validateInput(data);
// Async Result - use Result.await
const user = yield* Result.await(api.getUser(validated.userId));
// Async Result from tryPromise
const config = yield* Result.await(
Result.tryPromise(() => fetchConfig(user.configId))
);
return Result.ok({ validated, user, config });
});
// Sequential async operations
const result = await Result.gen(async function* () {
const token = yield* Result.await(authenticate(credentials));
const session = yield* Result.await(createSession(token));
const user = yield* Result.await(loadUser(session.userId));
return Result.ok({ session, user });
});
Utilities
Result.partition()
Splits an array of Results into separate arrays of success values and error values.
Result.partition<T, E>(
results: readonly Result<T, E>[]
): [T[], E[]]
Parameters
results
readonly Result<T, E>[]
required
Array of Results to partition
Returns
Tuple of [success values, error values]
Examples
const results = [
Result.ok(1),
Result.err("a"),
Result.ok(2),
Result.err("b"),
Result.ok(3),
];
const [oks, errs] = Result.partition(results);
// oks: [1, 2, 3]
// errs: ["a", "b"]
// Processing multiple operations
const userIds = ["1", "2", "3"];
const results = userIds.map(id => getUser(id));
const [users, errors] = Result.partition(results);
if (errors.length > 0) {
console.error("Failed to load some users:", errors);
}
console.log("Loaded users:", users);
Serialization
Result.serialize()
Converts a Result to a plain object for serialization (e.g., JSON, RPC, server actions).
Result.serialize<T, E>(
result: Result<T, E>
): SerializedResult<T, E>
Parameters
Returns
Plain object with status and either value (Ok) or error (Err)
Type Definitions
type SerializedOk<T> = {
status: "ok";
value: T;
};
type SerializedErr<E> = {
status: "error";
error: E;
};
type SerializedResult<T, E> = SerializedOk<T> | SerializedErr<E>;
Examples
// Serialize Ok
const serialized = Result.serialize(Result.ok(42));
// { status: "ok", value: 42 }
// Serialize Err
const serialized = Result.serialize(Result.err("failed"));
// { status: "error", error: "failed" }
// Server action (Next.js)
export async function getUserAction(id: string) {
const result = await getUser(id);
return Result.serialize(result); // Safe to send over RPC
}
Result.deserialize()
Rehydrates a serialized Result back into Ok/Err instances.
Result.deserialize<T, E>(
value: unknown
): Result<T, E | ResultDeserializationError>
Parameters
Value to deserialize (typically from RPC or JSON)
Returns
result
Result<T, E | ResultDeserializationError>
Deserialized Result if valid, otherwise Err<ResultDeserializationError>
Behavior
- Validates that the input is a valid serialized Result
- Returns
Ok or Err instance if valid
- Returns
Err<ResultDeserializationError> if invalid
Examples
// Valid serialized Result
const rpcResponse = { status: "ok", value: { id: "1", name: "Alice" } };
const result = Result.deserialize<User, AppError>(rpcResponse);
if (Result.isOk(result)) {
console.log(result.value); // User: { id: "1", name: "Alice" }
}
// Invalid input
const invalid = { foo: "bar" };
const result = Result.deserialize(invalid);
if (Result.isError(result) && ResultDeserializationError.is(result.error)) {
console.log("Bad input:", result.error.value);
}
// Client-side (Next.js)
const response = await getUserAction(id);
const result = Result.deserialize<User, AppError>(response);
return result.match({
ok: (user) => <UserProfile user={user} />,
err: (error) => <ErrorMessage error={error} />,
});
Type Guards
Result.isOk()
See ok() and err() for documentation.
Result.isError()
See ok() and err() for documentation.
See Also