参考にしました。ありがとうございます。
TypeScriptの異常系表現のいい感じの落とし所
[TypeScript] Liftsでサクッと関数型プログラミング - Pipeline/Switch式/Result型など
typescriptを書くときにエラーハンドリングをいい感じにしたいのでいろいろ調べてみました。
try catch だけじゃ困る理由
async function something () {
if(...) {
throw new Error('あるエラー')
}
if(...) {
throw new Error('別のエラー')
}
}
async function foo () {
try {
await something();
} catch (e) {
// どんなエラーかここでわからない。とりあえずエラーがおこったので、プログラムが中断させたことだけわかる。
}
}
class DoSomethingError extends Error {}で独自のエラータイプを作っても、catchでどのエラーが投げられたかわかりません。
例えばフォームを作るときにバリデーションエラーをthrow new Errorした場合、後日別の人がそのコードを触るとき
「このエラーはバリデーションエラーなのか、それともプログラムのエラーなのかわからん。
」
となってしまいがちです。同じ理由でnullを返すのもよくありませんね。
例外は例外的でなければならない
対策
either
関数型言語にはおなじみのeitherですね。
class Left<L, A> {
readonly value: L;
constructor(value: L) {
this.value = value;
}
isLeft(): this is Left<L, A> {
return true;
}
isRight(): this is Right<L, A> {
return false;
}
}
class Right<L, A> {
readonly value: A;
constructor(value: A) {
this.value = value;
}
isLeft(): this is Left<L, A> {
return false;
}
isRight(): this is Right<L, A> {
return true;
}
}
type Either<L, A> = Left<L, A> | Right<L, A>;
const left = <L, A>(l: L): Either<L, A> => {
return new Left(l);
};
const right = <L, A>(a: A): Either<L, A> => {
return new Right<L, A>(a);
};
// 使い方
const ERROR_TYPE_VALIDATION = 'error validation';
const ERROR_TYPE_OTHER = 'error other';
interface ErrorType {
type: typeof ERROR_TYPE_VALIDATION | typeof ERROR_TYPE_OTHER;
message: string;
}
async function something(param: string): Promise<Either<ErrorType, string>> {
if (param.length > 50) {
return left({type: ERROR_TYPE_VALIDATION, message: '50文字以内にしてください'});
}
if (param.indexOf('other') !== -1) {
return left({type: ERROR_TYPE_OTHER, message: 'otherの単語を含まないでください'});
}
return right('done');
}
async function foo() {
const result = await something('hoge');
if (result.isLeft()) {
if (result.value.type === ERROR_TYPE_VALIDATION) {
return result.value.message;
}
if (result.value.type === ERROR_TYPE_OTHER) {
return 'other error';
}
}
return result.value;
}
いい感じですね![]()
ただ、vscodeでなぜかleft({type: ERROR_TYPE_VALIDATION})としていても、コンパイルエラーが表示されません。。。tscでコンパイルするとやっとエラーになりました。(わかるひと教えてください(T_T))
配列かオブジェクトで表現する
golangのイディオムで以下のようなものがあると思います。
if _, err := foo; err != nil {
// 例外処理
}
こんな感じで
const ERROR_TYPE_VALIDATION = 'error validation';
const ERROR_TYPE_OTHER = 'error other';
interface ErrorType {
type: typeof ERROR_TYPE_VALIDATION | typeof ERROR_TYPE_OTHER;
message: string;
}
async function something(param: string): Promise<[string | undefined, ErrorType | undefined]> {
if (param.length > 50) {
return [undefined, {type: ERROR_TYPE_VALIDATION, message: '50文字以内にしてください'}];
}
if (param.indexOf('other') !== -1) {
return [undefined, {type: ERROR_TYPE_OTHER, message: 'otherの単語を含まないでください'}];
}
return ['done', undefined];
}
async function foo() {
const [result, error] = await something('hoge');
if (error) {
if (error.type === ERROR_TYPE_VALIDATION) {
return error.message;
}
if (error.type === ERROR_TYPE_OTHER) {
return 'other error';
}
}
if (!result) {
return 'something error';
}
return result;
}
オブジェクトでもいけますね
async function something(param: string): Promise<{result?: string; error?: ErrorType}> {
if (param.length > 50) {
return {error: {type: ERROR_TYPE_VALIDATION, message: '50文字以内にしてください'}};
}
if (param.indexOf('other') !== -1) {
return {error: {type: ERROR_TYPE_OTHER, message: 'otherの単語を含まないでください'}};
}
return {result: 'done'};
}
async function foo() {
const {result, error} = await something('hoge');
if (error) {
if (error.type === ERROR_TYPE_VALIDATION) {
return error.message;
}
if (error.type === ERROR_TYPE_OTHER) {
return 'other error';
}
}
if (!result) {
return 'something error';
}
return result;
}
配列の場合、分割代入の順番が間違ったりする可能性があるのが気になるのでもしかするとオブジェクトのほうがベターかもしれません。