Chaining Results

The andThen and orElse functions allow you to chain computations that may themselves return a Result. Unlike map and mapError which only transform values, these functions enable sequential operations where each step can succeed or fail.

andThen - Chaining Success Computations

The andThen function chains the next computation using the success value. If the original Result is a Failure, it is returned unchanged. Otherwise, the provided function is called, and its result is returned as-is.

Basic Usage

import { 
import Result
Result
} from '@praha/byethrow';
const
const result: Result.Result<number, never>
result
=
import Result
Result
.
const pipe: <Result.Result<3, never>, Result.Result<number, never>>(a: Result.Result<3, never>, ab: (a: Result.Result<3, never>) => Result.Result<number, never>) => Result.Result<number, never> (+25 overloads)
pipe
(
import Result
Result
.
const succeed: <3>(value: 3) => Result.Result<3, never> (+1 overload)
succeed
(3),
import Result
Result
.
const andThen: <Result.Result<3, never>, Result.Result<number, never>>(fn: (a: 3) => Result.Result<number, never>) => (result: Result.Result<3, never>) => Result.Result<number, never> (+1 overload)
andThen
((
value: 3
value
) =>
import Result
Result
.
const succeed: <number>(value: number) => Result.Result<number, never> (+1 overload)
succeed
(
value: 3
value
* 2)),
); // { type: 'Success', value: 6 }

When the Input is a Failure

If the input is a Failure, andThen does nothing and returns the Failure as-is:

import { 
import Result
Result
} from '@praha/byethrow';
const
const result: Result.Result<number, "error">
result
=
import Result
Result
.
const pipe: <Result.Result<never, "error">, Result.Result<number, "error">>(a: Result.Result<never, "error">, ab: (a: Result.Result<never, "error">) => Result.Result<number, "error">) => Result.Result<number, "error"> (+25 overloads)
pipe
(
import Result
Result
.
const fail: <"error">(error: "error") => Result.Result<never, "error"> (+1 overload)
fail
('error'),
import Result
Result
.
const andThen: <Result.Result<never, "error">, Result.Result<number, never>>(fn: (a: never) => Result.Result<number, never>) => (result: Result.Result<never, "error">) => Result.Result<number, "error"> (+1 overload)
andThen
((
value: never
value
) =>
import Result
Result
.
const succeed: <number>(value: number) => Result.Result<number, never> (+1 overload)
succeed
(
value: never
value
* 2)),
); // { type: 'Failure', error: 'error' }

When the Function Returns a Failure

The chained function can return a Failure, which propagates through the pipeline:

import { 
import Result
Result
} from '@praha/byethrow';
const
const result: Result.Result<never, string>
result
=
import Result
Result
.
const pipe: <Result.Result<3, never>, Result.Result<never, string>>(a: Result.Result<3, never>, ab: (a: Result.Result<3, never>) => Result.Result<never, string>) => Result.Result<never, string> (+25 overloads)
pipe
(
import Result
Result
.
const succeed: <3>(value: 3) => Result.Result<3, never> (+1 overload)
succeed
(3),
import Result
Result
.
const andThen: <Result.Result<3, never>, Result.Result<never, string>>(fn: (a: 3) => Result.Result<never, string>) => (result: Result.Result<3, never>) => Result.Result<never, string> (+1 overload)
andThen
((
value: 3
value
) =>
import Result
Result
.
const fail: <string>(error: string) => Result.Result<never, string> (+1 overload)
fail
('error: ' +
value: 3
value
)),
); // { type: 'Failure', error: 'error: 3' }

Example: Sequential Validation

A common use case is chaining multiple validation or processing steps:

import { 
import Result
Result
} from '@praha/byethrow';
type
type User = {
    id: string;
    name: string;
    email: string;
}
User
= {
id: string
id
: string;
name: string
name
: string;
email: string
email
: string };
declare const
const findUserById: (id: string) => Result.ResultAsync<User, "NotFound">
findUserById
: (
id: string
id
: string) =>
import Result
Result
.
type ResultAsync<T, E> = Promise<Result.Result<T, E>>

An asynchronous variant of Result , wrapped in a Promise.

@typeParamT - The type of the Success value.@typeParamE - The type of the Failure value.@example
import { Result } from '@praha/byethrow';

const fetchData = async (): Result.ResultAsync<string, Error> => {
  try {
    const data = await fetch('...');
    return { type: 'Success', value: await data.text() };
  } catch (err) {
    return { type: 'Failure', error: err as Error };
  }
};
@categoryCore Types
ResultAsync
<
type User = {
    id: string;
    name: string;
    email: string;
}
User
, 'NotFound'>;
declare const
const validateEmail: (user: User) => Result.Result<User, "InvalidEmail">
validateEmail
: (
user: User
user
:
type User = {
    id: string;
    name: string;
    email: string;
}
User
) =>
import Result
Result
.
type Result<T, E> = Result.Success<T> | Result.Failure<E>

A union type representing either a success or a failure.

@typeParamT - The type of the Success value.@typeParamE - The type of the Failure value.@example
import { Result } from '@praha/byethrow';

const doSomething = (): Result.Result<number, string> => {
  return Math.random() > 0.5
    ? { type: 'Success', value: 10 }
    : { type: 'Failure', error: 'Oops' };
};
@categoryCore Types
Result
<
type User = {
    id: string;
    name: string;
    email: string;
}
User
, 'InvalidEmail'>;
declare const
const saveUser: (user: User) => Result.ResultAsync<User, "SaveFailed">
saveUser
: (
user: User
user
:
type User = {
    id: string;
    name: string;
    email: string;
}
User
) =>
import Result
Result
.
type ResultAsync<T, E> = Promise<Result.Result<T, E>>

An asynchronous variant of Result , wrapped in a Promise.

@typeParamT - The type of the Success value.@typeParamE - The type of the Failure value.@example
import { Result } from '@praha/byethrow';

const fetchData = async (): Result.ResultAsync<string, Error> => {
  try {
    const data = await fetch('...');
    return { type: 'Success', value: await data.text() };
  } catch (err) {
    return { type: 'Failure', error: err as Error };
  }
};
@categoryCore Types
ResultAsync
<
type User = {
    id: string;
    name: string;
    email: string;
}
User
, 'SaveFailed'>;
const
const result: Result.Result<User, "NotFound" | "InvalidEmail" | "SaveFailed">
result
= await
import Result
Result
.
const pipe: <Result.Result<"user-123", never>, Result.ResultAsync<User, "NotFound">, Result.ResultAsync<User, "NotFound" | "InvalidEmail">, Result.ResultAsync<User, "NotFound" | "InvalidEmail" | "SaveFailed">>(a: Result.Result<"user-123", never>, ab: (a: Result.Result<"user-123", never>) => Result.ResultAsync<User, "NotFound">, bc: (b: Result.ResultAsync<User, "NotFound">) => Result.ResultAsync<User, "NotFound" | "InvalidEmail">, cd: (c: Result.ResultAsync<...>) => Result.ResultAsync<...>) => Result.ResultAsync<...> (+25 overloads)
pipe
(
import Result
Result
.
const succeed: <"user-123">(value: "user-123") => Result.Result<"user-123", never> (+1 overload)
succeed
('user-123'),
import Result
Result
.
const andThen: <Result.Result<"user-123", never>, Result.ResultAsync<User, "NotFound">>(fn: (a: "user-123") => Result.ResultAsync<User, "NotFound">) => (result: Result.Result<"user-123", never>) => Result.ResultAsync<User, "NotFound"> (+1 overload)
andThen
(
const findUserById: (id: string) => Result.ResultAsync<User, "NotFound">
findUserById
),
import Result
Result
.
const andThen: <Result.ResultAsync<User, "NotFound">, Result.Result<User, "InvalidEmail">>(fn: (a: User) => Result.Result<User, "InvalidEmail">) => (result: Result.ResultAsync<User, "NotFound">) => Result.ResultAsync<User, "NotFound" | "InvalidEmail"> (+1 overload)
andThen
(
const validateEmail: (user: User) => Result.Result<User, "InvalidEmail">
validateEmail
),
import Result
Result
.
const andThen: <Result.ResultAsync<User, "NotFound" | "InvalidEmail">, Result.ResultAsync<User, "SaveFailed">>(fn: (a: User) => Result.ResultAsync<User, "SaveFailed">) => (result: Result.ResultAsync<User, "NotFound" | "InvalidEmail">) => Result.ResultAsync<User, "NotFound" | "InvalidEmail" | "SaveFailed"> (+1 overload)
andThen
(
const saveUser: (user: User) => Result.ResultAsync<User, "SaveFailed">
saveUser
),
); // If any step fails, the pipeline short-circuits with that error

orElse - Recovering from Failures

The orElse function chains the next computation using the error value. If the original Result is a Success, it is returned unchanged. Otherwise, the provided function is called, and its result is returned as-is.

This is useful for error recovery - providing fallback values or retrying with different strategies.

Basic Usage

import { 
import Result
Result
} from '@praha/byethrow';
const
const result: Result.Result<0 | 42, never>
result
=
import Result
Result
.
const pipe: <Result.Result<42, never>, Result.Result<0 | 42, never>>(a: Result.Result<42, never>, ab: (a: Result.Result<42, never>) => Result.Result<0 | 42, never>) => Result.Result<0 | 42, never> (+25 overloads)
pipe
(
import Result
Result
.
const succeed: <42>(value: 42) => Result.Result<42, never> (+1 overload)
succeed
(42),
import Result
Result
.
const orElse: <Result.Result<42, never>, Result.Result<0, never>>(fn: (a: never) => Result.Result<0, never>) => (result: Result.Result<42, never>) => Result.Result<0 | 42, never> (+1 overload)
orElse
((
error: never
error
) =>
import Result
Result
.
const succeed: <0>(value: 0) => Result.Result<0, never> (+1 overload)
succeed
(0)),
); // { type: 'Success', value: 42 }

When the Input is a Failure

If the input is a Failure, orElse runs the recovery function:

import { 
import Result
Result
} from '@praha/byethrow';
const
const result: Result.Result<"default value", never>
result
=
import Result
Result
.
const pipe: <Result.Result<never, "original error">, Result.Result<"default value", never>>(a: Result.Result<never, "original error">, ab: (a: Result.Result<never, "original error">) => Result.Result<"default value", never>) => Result.Result<"default value", never> (+25 overloads)
pipe
(
import Result
Result
.
const fail: <"original error">(error: "original error") => Result.Result<never, "original error"> (+1 overload)
fail
('original error'),
import Result
Result
.
const orElse: <Result.Result<never, "original error">, Result.Result<"default value", never>>(fn: (a: "original error") => Result.Result<"default value", never>) => (result: Result.Result<never, "original error">) => Result.Result<"default value", never> (+1 overload)
orElse
((
error: "original error"
error
) =>
import Result
Result
.
const succeed: <"default value">(value: "default value") => Result.Result<"default value", never> (+1 overload)
succeed
('default value')),
); // { type: 'Success', value: 'default value' }

When the Recovery Function Returns a Failure

The recovery function can also return a Failure:

import { 
import Result
Result
} from '@praha/byethrow';
const
const result: Result.Result<never, string>
result
=
import Result
Result
.
const pipe: <Result.Result<never, "original error">, Result.Result<never, string>>(a: Result.Result<never, "original error">, ab: (a: Result.Result<never, "original error">) => Result.Result<never, string>) => Result.Result<never, string> (+25 overloads)
pipe
(
import Result
Result
.
const fail: <"original error">(error: "original error") => Result.Result<never, "original error"> (+1 overload)
fail
('original error'),
import Result
Result
.
const orElse: <Result.Result<never, "original error">, Result.Result<never, string>>(fn: (a: "original error") => Result.Result<never, string>) => (result: Result.Result<never, "original error">) => Result.Result<never, string> (+1 overload)
orElse
((
error: "original error"
error
) =>
import Result
Result
.
const fail: <string>(error: string) => Result.Result<never, string> (+1 overload)
fail
('new error: ' +
error: "original error"
error
)),
); // { type: 'Failure', error: 'new error: original error' }

Example: Fallback Strategy

A common pattern is implementing fallback strategies:

import { 
import Result
Result
} from '@praha/byethrow';
type
type Config = {
    apiUrl: string;
    timeout: number;
}
Config
= {
apiUrl: string
apiUrl
: string;
timeout: number
timeout
: number };
declare const
const loadConfigFromFile: () => Result.ResultAsync<Config, "FileNotFound">
loadConfigFromFile
: () =>
import Result
Result
.
type ResultAsync<T, E> = Promise<Result.Result<T, E>>

An asynchronous variant of Result , wrapped in a Promise.

@typeParamT - The type of the Success value.@typeParamE - The type of the Failure value.@example
import { Result } from '@praha/byethrow';

const fetchData = async (): Result.ResultAsync<string, Error> => {
  try {
    const data = await fetch('...');
    return { type: 'Success', value: await data.text() };
  } catch (err) {
    return { type: 'Failure', error: err as Error };
  }
};
@categoryCore Types
ResultAsync
<
type Config = {
    apiUrl: string;
    timeout: number;
}
Config
, 'FileNotFound'>;
declare const
const loadConfigFromEnv: () => Result.ResultAsync<Config, "EnvNotSet">
loadConfigFromEnv
: () =>
import Result
Result
.
type ResultAsync<T, E> = Promise<Result.Result<T, E>>

An asynchronous variant of Result , wrapped in a Promise.

@typeParamT - The type of the Success value.@typeParamE - The type of the Failure value.@example
import { Result } from '@praha/byethrow';

const fetchData = async (): Result.ResultAsync<string, Error> => {
  try {
    const data = await fetch('...');
    return { type: 'Success', value: await data.text() };
  } catch (err) {
    return { type: 'Failure', error: err as Error };
  }
};
@categoryCore Types
ResultAsync
<
type Config = {
    apiUrl: string;
    timeout: number;
}
Config
, 'EnvNotSet'>;
const
const config: Result.Result<Config, "EnvNotSet">
config
= await
import Result
Result
.
const pipe: <Result.ResultAsync<Config, "FileNotFound">, Result.ResultAsync<Config, "EnvNotSet">>(a: Result.ResultAsync<Config, "FileNotFound">, ab: (a: Result.ResultAsync<Config, "FileNotFound">) => Result.ResultAsync<Config, "EnvNotSet">) => Result.ResultAsync<Config, "EnvNotSet"> (+25 overloads)
pipe
(
const loadConfigFromFile: () => Result.ResultAsync<Config, "FileNotFound">
loadConfigFromFile
(),
import Result
Result
.
const orElse: <Result.ResultAsync<Config, "FileNotFound">, Result.ResultAsync<Config, "EnvNotSet">>(fn: (a: "FileNotFound") => Result.ResultAsync<Config, "EnvNotSet">) => (result: Result.ResultAsync<Config, "FileNotFound">) => Result.ResultAsync<Config, "EnvNotSet"> (+1 overload)
orElse
(() =>
const loadConfigFromEnv: () => Result.ResultAsync<Config, "EnvNotSet">
loadConfigFromEnv
()),
); // Try file first, then environment variables, else fail

Combining andThen and orElse

You can use both functions together to create complex flows with both success chaining and error recovery:

import { 
import Result
Result
} from '@praha/byethrow';
type
type User = {
    id: string;
    name: string;
}
User
= {
id: string
id
: string;
name: string
name
: string };
declare const
const fetchUserFromCache: (id: string) => Result.ResultAsync<User, "CacheMiss">
fetchUserFromCache
: (
id: string
id
: string) =>
import Result
Result
.
type ResultAsync<T, E> = Promise<Result.Result<T, E>>

An asynchronous variant of Result , wrapped in a Promise.

@typeParamT - The type of the Success value.@typeParamE - The type of the Failure value.@example
import { Result } from '@praha/byethrow';

const fetchData = async (): Result.ResultAsync<string, Error> => {
  try {
    const data = await fetch('...');
    return { type: 'Success', value: await data.text() };
  } catch (err) {
    return { type: 'Failure', error: err as Error };
  }
};
@categoryCore Types
ResultAsync
<
type User = {
    id: string;
    name: string;
}
User
, 'CacheMiss'>;
declare const
const fetchUserFromDb: (id: string) => Result.ResultAsync<User, "NotFound">
fetchUserFromDb
: (
id: string
id
: string) =>
import Result
Result
.
type ResultAsync<T, E> = Promise<Result.Result<T, E>>

An asynchronous variant of Result , wrapped in a Promise.

@typeParamT - The type of the Success value.@typeParamE - The type of the Failure value.@example
import { Result } from '@praha/byethrow';

const fetchData = async (): Result.ResultAsync<string, Error> => {
  try {
    const data = await fetch('...');
    return { type: 'Success', value: await data.text() };
  } catch (err) {
    return { type: 'Failure', error: err as Error };
  }
};
@categoryCore Types
ResultAsync
<
type User = {
    id: string;
    name: string;
}
User
, 'NotFound'>;
declare const
const validateUser: (user: User) => Result.Result<User, "Invalid">
validateUser
: (
user: User
user
:
type User = {
    id: string;
    name: string;
}
User
) =>
import Result
Result
.
type Result<T, E> = Result.Success<T> | Result.Failure<E>

A union type representing either a success or a failure.

@typeParamT - The type of the Success value.@typeParamE - The type of the Failure value.@example
import { Result } from '@praha/byethrow';

const doSomething = (): Result.Result<number, string> => {
  return Math.random() > 0.5
    ? { type: 'Success', value: 10 }
    : { type: 'Failure', error: 'Oops' };
};
@categoryCore Types
Result
<
type User = {
    id: string;
    name: string;
}
User
, 'Invalid'>;
const
const getValidatedUser: (id: string) => Result.ResultAsync<User, "NotFound" | "Invalid">
getValidatedUser
= (
id: string
id
: string) =>
import Result
Result
.
const pipe: <Result.ResultAsync<User, "CacheMiss">, Result.ResultAsync<User, "NotFound">, Result.ResultAsync<User, "NotFound" | "Invalid">>(a: Result.ResultAsync<User, "CacheMiss">, ab: (a: Result.ResultAsync<User, "CacheMiss">) => Result.ResultAsync<User, "NotFound">, bc: (b: Result.ResultAsync<User, "NotFound">) => Result.ResultAsync<User, "NotFound" | "Invalid">) => Result.ResultAsync<User, "NotFound" | "Invalid"> (+25 overloads)
pipe
(
const fetchUserFromCache: (id: string) => Result.ResultAsync<User, "CacheMiss">
fetchUserFromCache
(
id: string
id
),
import Result
Result
.
const orElse: <Result.ResultAsync<User, "CacheMiss">, Result.ResultAsync<User, "NotFound">>(fn: (a: "CacheMiss") => Result.ResultAsync<User, "NotFound">) => (result: Result.ResultAsync<User, "CacheMiss">) => Result.ResultAsync<User, "NotFound"> (+1 overload)
orElse
(() =>
const fetchUserFromDb: (id: string) => Result.ResultAsync<User, "NotFound">
fetchUserFromDb
(
id: string
id
)),
import Result
Result
.
const andThen: <Result.ResultAsync<User, "NotFound">, Result.Result<User, "Invalid">>(fn: (a: User) => Result.Result<User, "Invalid">) => (result: Result.ResultAsync<User, "NotFound">) => Result.ResultAsync<User, "NotFound" | "Invalid"> (+1 overload)
andThen
(
const validateUser: (user: User) => Result.Result<User, "Invalid">
validateUser
),
); // Try cache first, fall back to DB, then validate the result

References

FunctionPurpose
andThen(fn)Chain a computation using the success value
orElse(fn)Recover from a failure with a new computation