#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.@exampleimport { 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.@exampleimport { 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.@exampleimport { 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.@exampleimport { 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.@exampleimport { 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): booleanReturns 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.@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 result: Result.Result<string, ValidationError> result )) {
// TypeScript は result.error が ValidationError であることを知っている
var console: ConsoleThe 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 非同期操作もサポートしています。
非同期の Result は ResultAsync<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.@exampleimport { 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: PromiseConstructorRepresents 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.@exampleimport { 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: numberReturns 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.@exampleimport { 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: numberReturns 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 の非同期バリアント |
