#Quick Start
Get up and running with @praha/byethrow in minutes!
This guide will walk you through the essential concepts and usage patterns.
#Installation
Install the package using your preferred package manager:
npm install @praha/byethrowyarn add @praha/byethrowpnpm add @praha/byethrowbun add @praha/byethrowdeno add npm:@praha/byethrow#Basic Concepts
@praha/byethrow provides a Result type that represents the outcome of an operation that might fail. Instead of throwing exceptions, functions return a Result that can be either:
- Success: Contains a value of type
T - Failure: Contains an error of type
E
This approach makes error handling explicit and predictable.
#Your First Result
Let's start with a simple example:
import { import Result Result } from '@praha/byethrow';
// Creating a successful result
const const success: Result.Result<"Hello, World!", never> success = import Result Result .const succeed: <"Hello, World!">(value: "Hello, World!") => Result.Result<"Hello, World!", never> (+1 overload) succeed ('Hello, World!');
// Creating a failed result
const const failure: Result.Result<never, Error> failure = 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 ('Something went wrong'));
// Checking the result
if (import Result Result .const isSuccess: <Result.Result<"Hello, World!", never>>(result: Result.Result<"Hello, World!", never>) => result is Result.Success<"Hello, World!">Type guard to check if a
Result
is a
Success
.
@function@typeParamR - The type of the result to check.@paramresult - The Result to check.@returnstrue if the result is a Success , otherwise false.@exampleimport { Result } from '@praha/byethrow';
const result: Result.Result<number, string> = { type: 'Success', value: 10 };
if (Result.isSuccess(result)) {
console.log(result.value); // Safe access to value
}
@categoryType Guards isSuccess (const success: Result.Result<"Hello, World!", never> success )) {
var console: Console console .Console.log(...data: any[]): voidThe console.log() static method outputs a message to the console.
log (const success: Result.Success<"Hello, World!"> success .value: "Hello, World!" value ); // "Hello, World!"
}
if (import Result Result .const isFailure: <Result.Result<never, Error>>(result: Result.Result<never, Error>) => result is Result.Failure<Error>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.@exampleimport { 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 failure: Result.Result<never, Error> failure )) {
var console: Console console .Console.log(...data: any[]): voidThe console.log() static method outputs a message to the console.
log (const failure: Result.Failure<Error> failure .error: Error error .Error.message: string message ); // "Something went wrong"
}#Handling Operations That Might Fail
Use Result.fn to wrap functions that might throw exceptions:
import { import Result Result } from '@praha/byethrow';
const const parseNumber: (input: string) => Result.Result<number, Error> parseNumber = import Result Result .fn<(input: string) => number, Error>(options: {
try: (input: string) => number;
catch: (error: unknown) => Error;
}): (input: string) => Result.Result<number, Error> (+3 overloads)
export fn
Wraps a function that may throw and returns a new function that returns a
Result
or
ResultAsync
.
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.@returnsA new function that returns a Result or ResultAsync wrapping the original function's return value or the caught error.@exampleSync try-catch
import { Result } from '@praha/byethrow';
const fn = Result.fn({
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>
@exampleSync safe
import { Result } from '@praha/byethrow';
const fn = Result.fn({
safe: true,
try: (x: number) => x + 1,
});
const result = fn(1); // Result.Result<number, never>
@exampleAsync try-catch
import { Result } from '@praha/byethrow';
const fn = Result.fn({
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>
@exampleAsync safe
import { Result } from '@praha/byethrow';
const fn = Result.fn({
safe: true,
try: async () => await Promise.resolve('ok'),
});
const result = await fn(); // Result.ResultAsync<string, never>
@categoryCreators fn ({
try: (input: string) => number try : (input: string input : string) => {
const const num: number num = var Number: NumberConstructor
(value?: any) => number
An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
Number (input: string input );
if (var Number: NumberConstructorAn object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
Number .NumberConstructor.isNaN(number: unknown): booleanReturns a Boolean value that indicates whether a value is the reserved value NaN (not a
number). Unlike the global isNaN(), Number.isNaN() doesn't forcefully convert the parameter
to a number. Only values of the type number, that are also NaN, result in true.
@paramnumber A numeric value. isNaN (const num: number num )) {
throw new var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error ('Not a valid number');
}
return const num: number num ;
},
catch: (error: unknown) => Error catch : (error: unknown error ) => new var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error ('Failed to parse number', { ErrorOptions.cause?: unknown cause : error: unknown error }),
});
const const result: Result.Result<number, Error> result = const parseNumber: (input: string) => Result.Result<number, Error> parseNumber ('42');
if (import Result Result .const isSuccess: <Result.Result<number, Error>>(result: Result.Result<number, Error>) => result is Result.Success<number>Type guard to check if a
Result
is a
Success
.
@function@typeParamR - The type of the result to check.@paramresult - The Result to check.@returnstrue if the result is a Success , otherwise false.@exampleimport { Result } from '@praha/byethrow';
const result: Result.Result<number, string> = { type: 'Success', value: 10 };
if (Result.isSuccess(result)) {
console.log(result.value); // Safe access to value
}
@categoryType Guards isSuccess (const result: Result.Result<number, Error> result )) {
var console: Console console .Console.log(...data: any[]): voidThe console.log() static method outputs a message to the console.
log (const result: Result.Success<number> result .value: number value ); // 42
}#Transforming Values
Use Result.map to transform successful values:
import { import Result Result } from '@praha/byethrow';
const const double: (x: number) => number double = (x: number x : number) => x: number x * 2;
const const result: Result.Result<number, never> result = import Result Result .const pipe: <Result.Result<21, never>, Result.Result<number, never>>(a: Result.Result<21, never>, ab: (a: Result.Result<21, never>) => Result.Result<number, never>) => Result.Result<number, never> (+25 overloads) pipe (
import Result Result .const succeed: <21>(value: 21) => Result.Result<21, never> (+1 overload) succeed (21),
import Result Result .const map: <Result.Result<21, never>, number>(fn: (a: 21) => number) => (result: Result.Result<21, never>) => Result.Result<number, never> (+1 overload) map (const double: (x: number) => number double )
);
if (import Result Result .const isSuccess: <Result.Result<number, never>>(result: Result.Result<number, never>) => result is Result.Success<number>Type guard to check if a
Result
is a
Success
.
@function@typeParamR - The type of the result to check.@paramresult - The Result to check.@returnstrue if the result is a Success , otherwise false.@exampleimport { Result } from '@praha/byethrow';
const result: Result.Result<number, string> = { type: 'Success', value: 10 };
if (Result.isSuccess(result)) {
console.log(result.value); // Safe access to value
}
@categoryType Guards isSuccess (const result: Result.Result<number, never> result )) {
var console: Console console .Console.log(...data: any[]): voidThe console.log() static method outputs a message to the console.
log (const result: Result.Success<number> result .value: number value ); // 42
}#Chaining Operations
One of the most powerful features is chaining operations together using Result.pipe:
import { import Result Result } from '@praha/byethrow';
const const validateId: (id: string) => Result.Failure<Error> | Result.Success<string> validateId = (id: string id : string) => {
if (!id: string id .String.startsWith(searchString: string, position?: number): booleanReturns true if the sequence of elements of searchString converted to a String is the
same as the corresponding elements of this object (converted to a String) starting at
position. Otherwise returns false.
startsWith ('u')) {
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 ('Invalid ID format'));
}
return import Result Result .const succeed: <string>(value: string) => Result.Result<string, never> (+1 overload) succeed (id: string id );
};
const const findUser: (id: string) => Result.Failure<Error> | Result.Success<{
readonly id: "u123";
readonly name: "John Doe";
}>
findUser = (id: string id : string) => {
// Simulate a database lookup
if (id: string id === 'u123') {
return import Result Result .const succeed: <{
readonly id: "u123";
readonly name: "John Doe";
}>(value: {
readonly id: "u123";
readonly name: "John Doe";
}) => Result.Result<{
readonly id: "u123";
readonly name: "John Doe";
}, never> (+1 overload)
succeed ({ id: "u123" id , name: "John Doe" name : 'John Doe' });
}
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 ('User not found'));
};
const const toWelcome: (user: Result.InferSuccess<typeof findUser>) => string toWelcome = (user: {
readonly id: "u123";
readonly name: "John Doe";
}
user : import Result Result .type InferSuccess<T> = [T] extends [(...args: any[]) => Result.ResultMaybeAsync<infer U, any>] ? U : [T] extends [Result.ResultMaybeAsync<infer U, any>] ? U : neverInfers the
Success
value type T from a Result or a function returning a Result.
@typeParamT - A ResultMaybeAsync type or a function returning it.@exampleFrom a result object
import { Result } from '@praha/byethrow';
type R = Result.Result<number, string>;
type SuccessValue = Result.InferSuccess<R>; // number
@exampleFrom a function
import { Result } from '@praha/byethrow';
const fn = () => Promise.resolve({ type: 'Success', value: 123 } as const);
type SuccessValue = Result.InferSuccess<typeof fn>; // number
@categoryInfer Types InferSuccess <typeof const findUser: (id: string) => Result.Failure<Error> | Result.Success<{
readonly id: "u123";
readonly name: "John Doe";
}>
findUser >) => {
return `Welcome, ${user: {
readonly id: "u123";
readonly name: "John Doe";
}
user .name: "John Doe" name }!`;
};
// Chain multiple operations
const const result: Result.Result<string, Error> result = import Result Result .const pipe: <Result.Result<"u123", never>, Result.Result<string, Error>, Result.Result<{
readonly id: "u123";
readonly name: "John Doe";
}, Error>, Result.Result<string, Error>>(a: Result.Result<"u123", never>, ab: (a: Result.Result<"u123", never>) => Result.Result<string, Error>, bc: (b: Result.Result<string, Error>) => Result.Result<{
readonly id: "u123";
readonly name: "John Doe";
}, Error>, cd: (c: Result.Result<{
readonly id: "u123";
readonly name: "John Doe";
}, Error>) => Result.Result<...>) => Result.Result<...> (+25 overloads)
pipe (
import Result Result .const succeed: <"u123">(value: "u123") => Result.Result<"u123", never> (+1 overload) succeed ('u123'),
import Result Result .const andThen: <Result.Result<"u123", never>, Result.Failure<Error> | Result.Success<string>>(fn: (a: "u123") => Result.Failure<Error> | Result.Success<string>) => (result: Result.Result<"u123", never>) => Result.Result<string, Error> (+1 overload) andThen (const validateId: (id: string) => Result.Failure<Error> | Result.Success<string> validateId ),
import Result Result .const andThen: <Result.Result<string, Error>, Result.Failure<Error> | Result.Success<{
readonly id: "u123";
readonly name: "John Doe";
}>>(fn: (a: string) => Result.Failure<Error> | Result.Success<{
readonly id: "u123";
readonly name: "John Doe";
}>) => (result: Result.Result<string, Error>) => Result.Result<{
readonly id: "u123";
readonly name: "John Doe";
}, Error> (+1 overload)
andThen (const findUser: (id: string) => Result.Failure<Error> | Result.Success<{
readonly id: "u123";
readonly name: "John Doe";
}>
findUser ),
import Result Result .const map: <Result.Result<{
readonly id: "u123";
readonly name: "John Doe";
}, Error>, string>(fn: (a: {
readonly id: "u123";
readonly name: "John Doe";
}) => string) => (result: Result.Result<{
readonly id: "u123";
readonly name: "John Doe";
}, Error>) => Result.Result<string, Error> (+1 overload)
map (const toWelcome: (user: Result.InferSuccess<typeof findUser>) => string toWelcome )
);
if (import Result Result .const isSuccess: <Result.Result<string, Error>>(result: Result.Result<string, Error>) => result is Result.Success<string>Type guard to check if a
Result
is a
Success
.
@function@typeParamR - The type of the result to check.@paramresult - The Result to check.@returnstrue if the result is a Success , otherwise false.@exampleimport { Result } from '@praha/byethrow';
const result: Result.Result<number, string> = { type: 'Success', value: 10 };
if (Result.isSuccess(result)) {
console.log(result.value); // Safe access to value
}
@categoryType Guards isSuccess (const result: Result.Result<string, Error> result )) {
var console: Console console .Console.log(...data: any[]): voidThe console.log() static method outputs a message to the console.
log (const result: Result.Success<string> result .value: string value ); // "Welcome, John Doe!"
}#Error Handling
Handle errors gracefully with Result.orElse:
import { import Result Result } from '@praha/byethrow';
const const riskyOperation: () => Result.Result<never, Error> riskyOperation = () => 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 ('Operation failed'));
const const fallback: () => Result.Result<"Default value", never> fallback = () => import Result Result .const succeed: <"Default value">(value: "Default value") => Result.Result<"Default value", never> (+1 overload) succeed ('Default value');
const const result: Result.Result<"Default value", never> result = import Result Result .const pipe: <Result.Result<never, Error>, Result.Result<"Default value", never>>(a: Result.Result<never, Error>, ab: (a: Result.Result<never, Error>) => Result.Result<"Default value", never>) => Result.Result<"Default value", never> (+25 overloads) pipe (
const riskyOperation: () => Result.Result<never, Error> riskyOperation (),
import Result Result .const orElse: <Result.Result<never, Error>, Result.Result<"Default value", never>>(fn: (a: Error) => Result.Result<"Default value", never>) => (result: Result.Result<never, Error>) => Result.Result<"Default value", never> (+1 overload) orElse (const fallback: () => Result.Result<"Default value", never> fallback )
);
if (import Result Result .const isSuccess: <Result.Result<"Default value", never>>(result: Result.Result<"Default value", never>) => result is Result.Success<"Default value">Type guard to check if a
Result
is a
Success
.
@function@typeParamR - The type of the result to check.@paramresult - The Result to check.@returnstrue if the result is a Success , otherwise false.@exampleimport { Result } from '@praha/byethrow';
const result: Result.Result<number, string> = { type: 'Success', value: 10 };
if (Result.isSuccess(result)) {
console.log(result.value); // Safe access to value
}
@categoryType Guards isSuccess (const result: Result.Result<"Default value", never> result )) {
var console: Console console .Console.log(...data: any[]): voidThe console.log() static method outputs a message to the console.
log (const result: Result.Success<"Default value"> result .value: "Default value" value ); // "Default value"
}#Working with Async Operations
@praha/byethrow works seamlessly with asynchronous operations:
import { import Result Result } from '@praha/byethrow';
const const validateId: (id: string) => Result.Failure<Error> | Result.Success<string> validateId = (id: string id : string) => {
if (!id: string id .String.startsWith(searchString: string, position?: number): booleanReturns true if the sequence of elements of searchString converted to a String is the
same as the corresponding elements of this object (converted to a String) starting at
position. Otherwise returns false.
startsWith ('u')) {
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 ('Invalid ID format'));
}
return import Result Result .const succeed: <string>(value: string) => Result.Result<string, never> (+1 overload) succeed (id: string id );
};
const const findUser: (userId: string) => Result.ResultAsync<any, Error> findUser = import Result Result .fn<(userId: string) => Promise<any>, Error>(options: {
try: (userId: string) => Promise<any>;
catch: (error: unknown) => Error;
}): (userId: string) => Result.ResultAsync<any, Error> (+3 overloads)
export fn
Wraps a function that may throw and returns a new function that returns a
Result
or
ResultAsync
.
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.@returnsA new function that returns a Result or ResultAsync wrapping the original function's return value or the caught error.@exampleSync try-catch
import { Result } from '@praha/byethrow';
const fn = Result.fn({
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>
@exampleSync safe
import { Result } from '@praha/byethrow';
const fn = Result.fn({
safe: true,
try: (x: number) => x + 1,
});
const result = fn(1); // Result.Result<number, never>
@exampleAsync try-catch
import { Result } from '@praha/byethrow';
const fn = Result.fn({
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>
@exampleAsync safe
import { Result } from '@praha/byethrow';
const fn = Result.fn({
safe: true,
try: async () => await Promise.resolve('ok'),
});
const result = await fn(); // Result.ResultAsync<string, never>
@categoryCreators fn ({
try: (userId: string) => Promise<any> try : async (userId: string userId : string) => {
const const response: Response response = await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> fetch (`/api/users/${userId: string userId }`);
return await const response: Response response .Body.json(): Promise<any> json ();
},
catch: (error: unknown) => Error catch : (error: unknown error ) => new var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error ('Failed to find user', { ErrorOptions.cause?: unknown cause : error: unknown error }),
});
const const result: Result.Result<any, Error> result = await import Result Result .const pipe: <Result.Result<"u123", never>, Result.Result<string, Error>, Result.ResultAsync<any, Error>>(a: Result.Result<"u123", never>, ab: (a: Result.Result<"u123", never>) => Result.Result<string, Error>, bc: (b: Result.Result<string, Error>) => Result.ResultAsync<any, Error>) => Result.ResultAsync<any, Error> (+25 overloads) pipe (
import Result Result .const succeed: <"u123">(value: "u123") => Result.Result<"u123", never> (+1 overload) succeed ('u123'),
import Result Result .const andThen: <Result.Result<"u123", never>, Result.Failure<Error> | Result.Success<string>>(fn: (a: "u123") => Result.Failure<Error> | Result.Success<string>) => (result: Result.Result<"u123", never>) => Result.Result<string, Error> (+1 overload) andThen (const validateId: (id: string) => Result.Failure<Error> | Result.Success<string> validateId ),
import Result Result .const andThen: <Result.Result<string, Error>, Result.ResultAsync<any, Error>>(fn: (a: string) => Result.ResultAsync<any, Error>) => (result: Result.Result<string, Error>) => Result.ResultAsync<any, Error> (+1 overload) andThen (const findUser: (userId: string) => Result.ResultAsync<any, Error> findUser ),
);
if (import Result Result .const isSuccess: <Result.Result<any, Error>>(result: Result.Result<any, Error>) => result is Result.Success<any>Type guard to check if a
Result
is a
Success
.
@function@typeParamR - The type of the result to check.@paramresult - The Result to check.@returnstrue if the result is a Success , otherwise false.@exampleimport { Result } from '@praha/byethrow';
const result: Result.Result<number, string> = { type: 'Success', value: 10 };
if (Result.isSuccess(result)) {
console.log(result.value); // Safe access to value
}
@categoryType Guards isSuccess (const result: Result.Result<any, Error> result )) {
var console: Console console .Console.log(...data: any[]): voidThe console.log() static method outputs a message to the console.
log ('User data:', const result: Result.Success<any> result .value: any value );
}Happy coding with @praha/byethrow! 🚀
