• English
  • Result Type

    Before diving into the API, let's understand what the Result type is and why it's useful.

    What is a Result?

    A Result is a type that represents the outcome of an operation that might fail. It can be one of two variants:

    • Success: The operation succeeded and contains a value
    • Failure: The operation failed and contains an error

    This is fundamentally different from throwing exceptions. With Result, the possibility of failure is encoded in the type system itself.

    The Structure

    A Result is a simple object with a type discriminator:

    import { 
    import Result
    Result
    } from '@praha/byethrow';
    // A Success result const
    const success: Result.Success<number>
    success
    :
    import Result
    Result
    .
    type Success<T> = {
        readonly type: "Success";
        readonly value: T;
    }

    Represents a successful result.

    @typeParamT - The type of the successful value.@example
    import { Result } from '@praha/byethrow';
    
    const success: Result.Success<number> = {
      type: 'Success',
      value: 42,
    };
    @categoryCore Types
    Success
    <number> = {
    type: "Success"
    type
    : 'Success',
    value: number
    value
    : 42,
    }; // A Failure result const
    const failure: Result.Failure<string>
    failure
    :
    import Result
    Result
    .
    type Failure<E> = {
        readonly type: "Failure";
        readonly error: E;
    }

    Represents a failed result.

    @typeParamE - The type of the error.@example
    import { Result } from '@praha/byethrow';
    
    const failure: Result.Failure<string> = {
      type: 'Failure',
      error: 'Something went wrong',
    };
    @categoryCore Types
    Failure
    <string> = {
    type: "Failure"
    type
    : 'Failure',
    error: string
    error
    : 'Something went wrong',
    };

    The Union Type

    The Result.Result<T, E> type is a union of Success<T> and Failure<E>:

    import { 
    import Result
    Result
    } from '@praha/byethrow';
    // This function returns either a Success<number> or a Failure<string> const
    const divide: (a: number, b: number) => Result.Result<number, string>
    divide
    = (
    a: number
    a
    : number,
    b: number
    b
    : number):
    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
    <number, string> => {
    if (
    b: number
    b
    === 0) {
    return {
    type: "Failure"
    type
    : 'Failure',
    error: string
    error
    : 'Cannot divide by zero' };
    } return {
    type: "Success"
    type
    : 'Success',
    value: number
    value
    :
    a: number
    a
    /
    b: number
    b
    };
    }; const
    const result: Result.Result<number, string>
    result
    =
    const divide: (a: number, b: number) => Result.Result<number, string>
    divide
    (10, 2);
    // Type: Result.Result<number, string>

    Why Use Result Instead of Exceptions?

    1. Explicit Error Handling

    With exceptions, you never know if a function might throw:

    // ❌ Does this throw? We can't tell from the signature
    const 
    const parseConfig: (path: string) => Config
    parseConfig
    = (
    path: string
    path
    : string):
    type Config = {
        host: string;
        port: number;
    }
    Config
    => {
    // ... }

    With Result, it's clear:

    // ✅ The return type tells us this might fail
    const 
    const parseConfig: (path: string) => Result.Result<Config, ParseError>
    parseConfig
    = (
    path: string
    path
    : string):
    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 Config = {
        host: string;
        port: number;
    }
    Config
    ,
    class ParseError
    ParseError
    > => {
    // ... }

    2. Type-Safe Errors

    Exceptions lose type information. Result preserves it:

    import { 
    import Result
    Result
    } from '@praha/byethrow';
    type
    type ValidationError = {
        field: string;
        message: string;
    }
    ValidationError
    = {
    field: string
    field
    : string;
    message: string
    message
    : string };
    const
    const validateEmail: (email: string) => Result.Result<string, ValidationError>
    validateEmail
    = (
    email: string
    email
    : string):
    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
    <string,
    type ValidationError = {
        field: string;
        message: string;
    }
    ValidationError
    > => {
    if (!
    email: string
    email
    .
    String.includes(searchString: string, position?: number): boolean

    Returns true if searchString appears as a substring of the result of converting this object to a String, at one or more positions that are greater than or equal to position; otherwise, returns false.

    @paramsearchString search string@paramposition If position is undefined, 0 is assumed, so as to search all of the String.
    includes
    ('@')) {
    return
    import Result
    Result
    .
    const fail: <{
        readonly field: "email";
        readonly message: "Invalid email format";
    }>(error: {
        readonly field: "email";
        readonly message: "Invalid email format";
    }) => Result.Result<never, {
        readonly field: "email";
        readonly message: "Invalid email format";
    }> (+1 overload)
    fail
    ({
    field: "email"
    field
    : 'email',
    message: "Invalid email format"
    message
    : 'Invalid email format' });
    } return
    import Result
    Result
    .
    const succeed: <string>(value: string) => Result.Result<string, never> (+1 overload)
    succeed
    (
    email: string
    email
    );
    }; const
    const result: Result.Result<string, ValidationError>
    result
    =
    const validateEmail: (email: string) => Result.Result<string, ValidationError>
    validateEmail
    ('test');
    if (
    import Result
    Result
    .
    const isFailure: <Result.Result<string, ValidationError>>(result: Result.Result<string, ValidationError>) => result is Result.Failure<ValidationError>

    Type guard to check if a Result is a Failure .

    @function@typeParamR - The type of the result to check.@paramresult - The Result to check.@returnstrue if the result is a Failure , otherwise false.@example
    import { Result } from '@praha/byethrow';
    
    const result: Result.Result<number, string> = { type: 'Failure', error: 'Something went wrong' };
    if (Result.isFailure(result)) {
      console.error(result.error); // Safe access to error
    }
    @categoryType Guards
    isFailure
    (
    const result: Result.Result<string, ValidationError>
    result
    )) {
    // TypeScript knows result.error is ValidationError
    var console: Console
    console
    .
    Console.log(...data: any[]): void

    The console.log() static method outputs a message to the console.

    MDN Reference

    log
    (`Error in ${
    const result: Result.Failure<ValidationError>
    result
    .
    error: ValidationError
    error
    .
    field: string
    field
    }: ${
    const result: Result.Failure<ValidationError>
    result
    .
    error: ValidationError
    error
    .
    message: string
    message
    }`);
    }

    3. Composable

    Results can be easily chained and composed (we'll cover this in later sections):

    import { 
    import Result
    Result
    } from '@praha/byethrow';
    const
    const result: Result.ResultAsync<User, ValidationError | TransformError | SaveError>
    result
    =
    import Result
    Result
    .
    const pipe: <Result.Result<{
        id: string;
        name: string;
    }, never>, Result.Result<User, ValidationError>, Result.Result<User, ValidationError | TransformError>, Result.ResultAsync<User, ValidationError | TransformError | SaveError>>(a: Result.Result<{
        id: string;
        name: string;
    }, never>, ab: (a: Result.Result<{
        id: string;
        name: string;
    }, never>) => Result.Result<User, ValidationError>, bc: (b: Result.Result<...>) => Result.Result<...>, cd: (c: Result.Result<...>) => Result.ResultAsync<...>) => Result.ResultAsync<...> (+25 overloads)
    pipe
    (
    import Result
    Result
    .
    const succeed: <{
        id: string;
        name: string;
    }>(value: {
        id: string;
        name: string;
    }) => Result.Result<{
        id: string;
        name: string;
    }, never> (+1 overload)
    succeed
    (
    const input: {
        id: string;
        name: string;
    }
    input
    ),
    import Result
    Result
    .
    const andThen: <Result.Result<{
        id: string;
        name: string;
    }, never>, Result.Result<User, ValidationError>>(fn: (a: {
        id: string;
        name: string;
    }) => Result.Result<User, ValidationError>) => (result: Result.Result<{
        id: string;
        name: string;
    }, never>) => Result.Result<User, ValidationError> (+1 overload)
    andThen
    (
    const validate: (value: User) => Result.Result<User, ValidationError>
    validate
    ),
    import Result
    Result
    .
    const andThen: <Result.Result<User, ValidationError>, Result.Result<User, TransformError>>(fn: (a: User) => Result.Result<User, TransformError>) => (result: Result.Result<User, ValidationError>) => Result.Result<User, ValidationError | TransformError> (+1 overload)
    andThen
    (
    const transform: (value: User) => Result.Result<User, TransformError>
    transform
    ),
    import Result
    Result
    .
    const andThen: <Result.Result<User, ValidationError | TransformError>, Result.ResultAsync<User, SaveError>>(fn: (a: User) => Result.ResultAsync<User, SaveError>) => (result: Result.Result<User, ValidationError | TransformError>) => Result.ResultAsync<User, ValidationError | TransformError | SaveError> (+1 overload)
    andThen
    (
    const save: (value: User) => Result.ResultAsync<User, SaveError>
    save
    ),
    ); // Type: Result.ResultAsync<User, ValidationError | TransformError | SaveError>

    Async Results

    @praha/byethrow also supports asynchronous operations. Asynchronous Result is a type alias ResultAsync<T, E>, representing Promise<Result<T, E>>:

    import { 
    import Result
    Result
    } from '@praha/byethrow';
    // ResultAsync is just Promise<Result<T, E>> type
    type ResultAsync<T, E> = Promise<Result.Result<T, E>>
    ResultAsync
    <
    function (type parameter) T in type ResultAsync<T, E>
    T
    ,
    function (type parameter) E in type ResultAsync<T, E>
    E
    > =
    interface Promise<T>

    Represents the completion of an asynchronous operation

    Promise
    <
    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
    <
    function (type parameter) T in type ResultAsync<T, E>
    T
    ,
    function (type parameter) E in type ResultAsync<T, E>
    E
    >>;
    // The library handles both sync and async seamlessly const
    const asyncResult: Result.ResultAsync<number, never>
    asyncResult
    =
    import Result
    Result
    .
    const succeed: <Promise<number>>(value: Promise<number>) => Result.ResultAsync<number, never> (+1 overload)
    succeed
    (
    var Promise: PromiseConstructor

    Represents the completion of an asynchronous operation

    Promise
    .
    PromiseConstructor.resolve<number>(value: number): Promise<number> (+2 overloads)

    Creates a new resolved promise for the provided value.

    @paramvalue A promise.@returnsA promise whose internal state matches the provided promise.
    resolve
    (42));
    // Type: Result.ResultAsync<number, never> const
    const resolved: Result.Result<number, never>
    resolved
    = await
    const asyncResult: Result.ResultAsync<number, never>
    asyncResult
    ;
    // Type: Result.Result<number, never>

    Seamless Sync/Async Chaining

    One of the powerful features of @praha/byethrow is that you can seamlessly chain synchronous and asynchronous Results together. When you mix sync and async operations in a pipeline, the result automatically becomes a ResultAsync:

    import { 
    import Result
    Result
    } from '@praha/byethrow';
    // Sync function const
    const validate: (input: string) => Result.Result<string, Error>
    validate
    = (
    input: string
    input
    : string):
    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
    <string, Error> => {
    if (
    input: string
    input
    .
    String.length: number

    Returns the length of a String object.

    length
    === 0) {
    return
    import Result
    Result
    .
    const fail: <Error>(error: Error) => Result.Result<never, Error> (+1 overload)
    fail
    (new
    var Error: ErrorConstructor
    new (message?: string, options?: ErrorOptions) => Error (+1 overload)
    Error
    ('Input is empty'));
    } return
    import Result
    Result
    .
    const succeed: <string>(value: string) => Result.Result<string, never> (+1 overload)
    succeed
    (
    input: string
    input
    );
    }; // Async function const
    const fetchData: (input: string) => Result.ResultAsync<number, Error>
    fetchData
    = async (
    input: string
    input
    : 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
    <number, Error> => {
    // Simulating an API call return
    import Result
    Result
    .
    const succeed: <number>(value: number) => Result.Result<number, never> (+1 overload)
    succeed
    (
    input: string
    input
    .
    String.length: number

    Returns the length of a String object.

    length
    );
    }; // Sync and async can be chained together seamlessly const
    const result: Result.ResultAsync<number, Error>
    result
    =
    import Result
    Result
    .
    const pipe: <Result.Result<"hello", never>, Result.Result<string, Error>, Result.ResultAsync<number, Error>, Result.ResultAsync<number, Error>>(a: Result.Result<"hello", never>, ab: (a: Result.Result<"hello", never>) => Result.Result<string, Error>, bc: (b: Result.Result<string, Error>) => Result.ResultAsync<number, Error>, cd: (c: Result.ResultAsync<number, Error>) => Result.ResultAsync<number, Error>) => Result.ResultAsync<...> (+25 overloads)
    pipe
    (
    import Result
    Result
    .
    const succeed: <"hello">(value: "hello") => Result.Result<"hello", never> (+1 overload)
    succeed
    ('hello'),
    import Result
    Result
    .
    const andThen: <Result.Result<"hello", never>, Result.Result<string, Error>>(fn: (a: "hello") => Result.Result<string, Error>) => (result: Result.Result<"hello", never>) => Result.Result<string, Error> (+1 overload)
    andThen
    (
    const validate: (input: string) => Result.Result<string, Error>
    validate
    ), // sync
    import Result
    Result
    .
    const andThen: <Result.Result<string, Error>, Result.ResultAsync<number, Error>>(fn: (a: string) => Result.ResultAsync<number, Error>) => (result: Result.Result<string, Error>) => Result.ResultAsync<number, Error> (+1 overload)
    andThen
    (
    const fetchData: (input: string) => Result.ResultAsync<number, Error>
    fetchData
    ), // async - from here, the pipeline becomes async
    import Result
    Result
    .
    const andThen: <Result.ResultAsync<number, Error>, Result.Result<number, never>>(fn: (a: number) => Result.Result<number, never>) => (result: Result.ResultAsync<number, Error>) => Result.ResultAsync<number, Error> (+1 overload)
    andThen
    ((
    n: number
    n
    ) =>
    import Result
    Result
    .
    const succeed: <number>(value: number) => Result.Result<number, never> (+1 overload)
    succeed
    (
    n: number
    n
    * 2)), // sync, but still in async context
    ); // Type: Result.ResultAsync<number, Error>

    References

    FunctionPurpose
    Success<T>Represents a successful result
    Failure<T>Represents a failed result
    Result<T, E>A union type of Success or Failure
    ResultAsync<T, E>An asynchronous variant of Result