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: <ValidationError>(result: Result.Result<unknown, ValidationError>) => result is Result.Failure<ValidationError>

Type guard to check if a Result is a Failure .

@function@typeParamE - The type of the error value.@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

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
//   Error: Whoops, something bad happened
//     at [eval]:5:15
//     at Script.runInThisContext (node:vm:132:18)
//     at Object.runInThisContext (node:vm:309:38)
//     at node:internal/process/execution:77:19
//     at [eval]-wrapper:6:22
//     at evalScript (node:internal/process/execution:76:60)
//     at node:internal/main/eval_string:23:3

const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);

myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err

const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
@seesource
console
.
Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0 .1.100
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