---
title: Transforming and chaining
description: Use map, mapError, andThen, and recovery while preserving precise success and error unions.
---

## Transform success with `map`

```ts
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`

```ts
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`

```ts
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:

```ts
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:

```ts
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`](/core/generator-composition#asynchronous-workflows) 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:

```ts
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:

```ts
const recovered = loadRemoteConfig().tryRecover(() => Result.ok(defaultConfigPath));
// Result<RemoteConfig | string, never>
```

Use `tryRecoverAsync` when recovery returns `Promise<Result<...>>`:

```ts
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:

```ts
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`.
