logo
byethrow
Guide
Examples
API Reference
Guide
Examples
API Reference
logo
byethrow

Getting Started

Introduction
Why byethrow?
Quick Start
Importing Result

Best Practices

Result vs throw
Custom Error
Pattern Matching

Tools & Integrations

MCP Server
ESLint Plugin

Last Updated: 2025/10/13 08:39:30

Previous PageImporting Result
Next PageCustom Error

#Result vs throw

You may have encountered opinions like: "Since JavaScript can throw errors anywhere and it's impossible to manage everything with Result, there's no point in introducing Result at all."

However, we don't necessarily agree with this perspective. The key insight is that Result should only handle "anticipated errors" - there's no need to wrap every possible error in a Result.

#The Philosophy: Anticipated vs Unexpected Errors

The distinction between what should be handled with Result versus what should be allowed to throw lies in understanding the nature of the error:

#Anticipated Errors (Use Result)

These are errors that are part of your application's business logic and should be handled explicitly:

// Example of a post deletion function
type 
type PostDeleteError = PostNotFoundError | PostPermissionError | PostAlreadyDeletedError
PostDeleteError
= (
|
class PostNotFoundError
PostNotFoundError
|
class PostPermissionError
PostPermissionError
|
class PostAlreadyDeletedError
PostAlreadyDeletedError
); const
const deletePost: (postId: string) => Result.ResultAsync<void, PostDeleteError>
deletePost
= async (
postId: string
postId
: 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
<void,
type PostDeleteError = PostNotFoundError | PostPermissionError | PostAlreadyDeletedError
PostDeleteError
> => {
// Business logic errors that should be handled by the application }

#Unexpected Errors (Let them throw)

These are infrastructure-level or truly unexpected errors:

  • Database connection failures
  • Network timeouts
  • Out of memory errors
  • Unknown exceptions
// Example of an infrastructure-level function
const 
const connectToDatabase: () => Promise<Database>
connectToDatabase
= async ():
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<Database> => {
// This function may throw errors like connection failures, timeouts, etc. };

These should be allowed to throw and be caught by infrastructure-level error handling (like Sentry).

#When You Need Better Stack Traces: Using Result.try

However, when you want more detailed stack traces for debugging purposes, we recommend using Result.try to wrap unexpected errors with custom error classes. This approach gives you application-level stack traces instead of library-level ones.

#Defining Custom Error Classes

First, define a custom error class for unexpected errors:

TIP

For more details about @praha/error-factory, see the Custom Error page.

import { 
const ErrorFactory: {
    <Name extends string, Message extends string, Fields extends ErrorFields>(props: {
        name: Name;
        message: Message | ((fields: Fields) => Message);
        fields?: Fields;
    }): ErrorConstructor<Name, Message, Fields>;
    fields<Fields extends ErrorFields>(): Fields;
}
ErrorFactory
} from '@praha/error-factory';
class
class UnexpectedError
UnexpectedError
extends
ErrorFactory<"UnexpectedError", "An unexpected error occurred", ErrorFields>(props: {
    name: "UnexpectedError";
    message: "An unexpected error occurred" | ((fields: ErrorFields) => "An unexpected error occurred");
    fields?: ErrorFields | undefined;
}): (new (options?: ErrorOptions) => Error & Readonly<{
    name: "UnexpectedError";
    message: "An unexpected error occurred";
}>) & {
    name: "UnexpectedError";
}
ErrorFactory
({
name: "UnexpectedError"
name
: 'UnexpectedError',
message: "An unexpected error occurred" | ((fields: ErrorFields) => "An unexpected error occurred")
message
: 'An unexpected error occurred',
}) {}

#Using Result.try for Better Error Handling

import { 
import Result
Result
} from '@praha/byethrow';
// Wrap potentially throwing operations const
const safeDatabaseOperation: (id: string) => Result.ResultAsync<string, UnexpectedError>
safeDatabaseOperation
=
import Result
Result
.
try<(id: string) => Promise<string>, UnexpectedError>(options: {
    try: (id: string) => Promise<string>;
    catch: (error: unknown) => UnexpectedError;
}): (id: string) => Result.ResultAsync<string, UnexpectedError> (+7 overloads)
export try

Wraps a function execution (sync or async) or a Promise in a Result or ResultAsync type, capturing errors and returning them in a structured way.

You can use either a custom catch handler or rely on the safe: true option to assume the function cannot throw.

@function@typeParamT - The function type to execute (sync or async) or a Promise type.@typeParamE - The error type to return if catch is used.@example

Sync try-catch

import { Result } from '@praha/byethrow';

const fn = Result.try({
try: (x: number) => {
if (x < 0) throw new Error('Negative!');
return x * 2;
},
catch: (error) => new Error('Oops!', { cause: error }),
});

const result = fn(5); // Result.Result<number, Error>
@example

Sync try-catch with immediate execution

import { Result } from '@praha/byethrow';

const result = Result.try({
immediate: true,
try: () => {
const x = Math.random() * 10 - 5;
if (x < 0) throw new Error('Negative!');
return x * 2;
},
catch: (error) => new Error('Oops!', { cause: error }),
});

// result is Result<number, Error>
@example

Sync safe

import { Result } from '@praha/byethrow';

const fn = Result.try({
safe: true,
try: (x: number) => x + 1,
});

const result = fn(1); // Result.Result<number, never>
@example

Sync safe with immediate execution

import { Result } from '@praha/byethrow';

const result = Result.try({
safe: true,
immediate: true,
try: () => Math.random() + 1,
});

// result is Result<number, never>
@example

Async try-catch

import { Result } from '@praha/byethrow';

const fn = Result.try({
try: async (id: string) => await fetch(`/api/data/${id}`),
catch: (error) => new Error('Oops!', { cause: error }),
});

const result = await fn('abc'); // Result.ResultAsync<Response, Error>
@example

Async try-catch with immediate execution

import { Result } from '@praha/byethrow';

const result = Result.try({
immediate: true,
try: () => fetch('/api/data'),
catch: (error) => new Error('Fetch failed', { cause: error }),
});

// result is ResultAsync<Response, Error>
@example

Async safe

import { Result } from '@praha/byethrow';

const fn = Result.try({
safe: true,
try: async () => await Promise.resolve('ok'),
});

const result = await fn(); // Result.ResultAsync<string, never>
@example

Async safe with immediate execution

import { Result } from '@praha/byethrow';

const result = Result.try({
safe: true,
immediate: true,
try: () => Promise.resolve('ok'),
});

// result is ResultAsync<string, never>
@categoryCreators
try
({
try: (id: string) => Promise<string>
try
: (
id: string
id
: string) => {
// This might throw database query errors, network errors, etc. return
const performDatabaseOperation: (id: string) => Promise<string>
performDatabaseOperation
(
id: string
id
);
},
catch: (error: unknown) => UnexpectedError
catch
: (
error: unknown
error
) => new
constructor UnexpectedError(options?: ErrorOptions): UnexpectedError
UnexpectedError
({
ErrorOptions.cause?: unknown
cause
:
error: unknown
error
}),
}); // Usage const
const result: Result.Result<string, UnexpectedError>
result
= await
const safeDatabaseOperation: (id: string) => Result.ResultAsync<string, UnexpectedError>
safeDatabaseOperation
('123');
if (
import Result
Result
.
const isFailure: <UnexpectedError>(result: Result.Result<unknown, UnexpectedError>) => result is Result.Failure<UnexpectedError>

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, UnexpectedError>
result
)) {
// You now have a clean UnexpectedError with your application's stack trace // instead of deep library stack traces
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.error(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stderr 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 code = 5;
console.error('error #%d', code);
// Prints: error #5, to stderr
console.error('error', code);
// Prints: error 5, to stderr

If formatting elements (e.g. %d) are not found in the first string then util.inspect() is called on each argument and the resulting string values are concatenated. See util.format() for more information.

@sincev0 .1.100
error
(
const result: Result.Failure<UnexpectedError>
result
.
error: UnexpectedError
error
.
Error.stack?: string | undefined
stack
);
// Original error is still accessible
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.error(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stderr 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 code = 5;
console.error('error #%d', code);
// Prints: error #5, to stderr
console.error('error', code);
// Prints: error 5, to stderr

If formatting elements (e.g. %d) are not found in the first string then util.inspect() is called on each argument and the resulting string values are concatenated. See util.format() for more information.

@sincev0 .1.100
error
(
const result: Result.Failure<UnexpectedError>
result
.
error: UnexpectedError
error
.
Error.cause?: unknown
cause
);
}

#Benefits of This Approach

  1. Clean Stack Traces: You get stack traces that point to your application code, not deep into library internals
  2. Error Context: You can add meaningful context to errors while preserving the original error
  3. Consistent Error Handling: All errors, whether anticipated or unexpected, can be handled through the Result interface when needed
  4. Debugging: The original error is still accessible through the cause property for debugging purposes

#Conclusion

The goal isn't to eliminate all throws in favor of Result, but to use each approach where it's most appropriate. Result excels at handling expected, business-level errors that require explicit handling, while throw remains the right choice for unexpected system errors that should be handled at the infrastructure level.

This hybrid approach gives you the benefits of explicit error handling where it matters most, without the burden of wrapping every possible error in your application.