Result 型について

API の詳細に入る前に、Result 型とは何か、なぜ便利なのかを理解しましょう。

Result とは?

Result は、失敗する可能性のある操作の結果を表す型です。 以下の2つの状態のいずれかになります。

  • Success:操作が成功し、値を含んでいます
  • Failure:操作が失敗し、エラーを含んでいます

これは例外を投げる方法とは根本的に異なります。 Result では、失敗の可能性が型システム自体に含まれています。

構造

Result は識別可能な type プロパティを持つシンプルなオブジェクトです。

import { 
import Result
Result
} from '@praha/byethrow';
// Success の結果 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,
}; // Failure の結果 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',
};

ユニオン型

Result.Result<T, E> 型は Success<T>Failure<E> のユニオン型です。

import { 
import Result
Result
} from '@praha/byethrow';
// この関数は Success<number> または 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);
// 型: Result.Result<number, string>

なぜ例外の代わりに Result を使うのか?

1. 明示的なエラーハンドリング

例外の場合、関数がエラーを投げるかどうかはわかりません。

// ❌ エラーを投げるかどうか、シグネチャからはわからない
const 
const parseConfig: (path: string) => Config
parseConfig
= (
path: string
path
: string):
type Config = {
    host: string;
    port: number;
}
Config
=> {
// ... }

Result を使えば、関数がエラーを返す可能性があることが明確になります。

// ✅ 戻り値の型から失敗する可能性があることがわかる
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. 型安全なエラー

例外は型情報を失います。Result は型情報を保持します。

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 は result.error が 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. 合成可能

Result は簡単にチェーンして合成できます(これは後のセクションで詳しく解説します)。

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
),
); // 型: Result.ResultAsync<User, ValidationError | TransformError | SaveError>

非同期 Result

@praha/byethrow 非同期操作もサポートしています。 非同期の ResultResultAsync<T, E> という型エイリアスで、Promise<Result<T, E>> を表します。

import { 
import Result
Result
} from '@praha/byethrow';
// ResultAsync は単に 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
>>;
// ライブラリは同期と非同期の両方をシームレスに扱います 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));
// 型: Result.ResultAsync<number, never> const
const resolved: Result.Result<number, never>
resolved
= await
const asyncResult: Result.ResultAsync<number, never>
asyncResult
;
// 型: Result.Result<number, never>

シームレスな同期/非同期チェーン

@praha/byethrow の強力な機能の1つは、同期と非同期の Result をシームレスにチェーンできることです。 パイプライン内で同期と非同期の操作を混在させると、結果は自動的に ResultAsync になります。

import { 
import Result
Result
} from '@praha/byethrow';
// 同期関数 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
);
}; // 非同期関数 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> => {
// API 呼び出しをシミュレート 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
);
}; // 同期と非同期はシームレスにチェーンできます 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
), // 同期
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
), // 非同期 - ここからパイプラインは非同期になります
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)), // 同期だが、非同期コンテキスト内
); // 型: Result.ResultAsync<number, Error>

リファレンス

関数目的
Success<T>成功した結果を表す
Failure<T>失敗した結果を表す
Result<T, E>Success または Failure のユニオン型
ResultAsync<T, E>Result の非同期バリアント