Transforming and chaining
Use map, mapError, andThen, and recovery while preserving precise success and error unions.
Transform success with map
const name = findUser(id).map((user) => user.name);
// Result<string, FindUserError>
map runs only for Ok. Returning a Result from map nests it; use andThen to flatten a Result-returning callback.
Transform errors with mapError
const user = queryUser(id).mapError(
(error) => new LoadUserFailed({ cause: error, message: "Could not load user" }),
);
// Result<User, LoadUserFailed>
Use this at abstraction boundaries to translate lower-level errors into the vocabulary the caller owns.
Chain with andThen
const result = parseInput(raw).andThen(validateInput).andThen(saveInput);
// Result<SavedInput, ParseError | ValidationError | SaveError>
andThen runs only for Ok and unions the existing and next error types. Use the static Result.andThenAsync when the next operation returns a Promise of Result:
const result = await Result.andThenAsync(parseInput(raw), (input) => saveInput(input));
Compose Promise<Result> pipelines
When an operation already returns Promise<Result<...>>, Promise chaining composes with static, data-last combinators:
const postCount = await fetchUser(userId)
.then(Result.andThenAsync((user: User) => fetchPosts(user.id)))
.then(Result.map((posts: ReadonlyArray<Post>) => posts.length));
// Result<number, UserNotFound | FetchPostsFailed>
Promise.then unwraps each outer Promise. Result.andThenAsync runs the next asynchronous operation only for Ok, while Result.map transforms the eventual success. Both error types remain visible.
Use this form for a short pipeline. Prefer Result.gen with Result.await when a workflow has several named steps or needs local intermediate values.
Recover with tryRecover
Recovery runs only for Err and must return a Result:
const user = findUser(id).tryRecover((error) =>
error._tag === "UserNotFound" ? Result.ok(guestUser) : Result.err(error),
);
Recovery may introduce a different success type. The existing success is preserved:
const recovered = loadRemoteConfig().tryRecover(() => Result.ok(defaultConfigPath));
// Result<RemoteConfig | string, never>
Use tryRecoverAsync when recovery returns Promise<Result<...>>:
const user = await fetchUser(userId).then(
Result.tryRecoverAsync(async (error: FetchUserError) =>
error._tag === "NetworkUnavailable" ? await readCachedUser(userId) : Result.err(error),
),
);
// Result<User, UserNotFound | CacheMiss>
The async callback still returns a Result. Rejection is a callback defect and becomes Panic; capture expected Promise failure inside the recovery operation.
Static and pipeable forms
Most combinators support data-first and data-last calls:
Result.map(result, (value) => value.id);
Result.map((value: User) => value.id)(result);
Result.andThen(result, validateUser);
Result.andThen(validateUser)(result);
This supports method chains, direct calls, and functional pipelines without separate APIs.
Callback safety
Callbacks passed to mapping, chaining, and recovery combinators are assumed not to throw. If one does, the operation throws a Panic with the original exception as cause.