Typescript await使用時のcatchの返り値について

枕を半年くらい洗わずに使っていたら後頭部にできものができました。
どうも、伊藤くんです。

いやはや久しぶりの投稿です。
今回は、後頭部にできものができたということなので、Typescriptの構文についてメモしておきます。

事象

例えば、以下のようなコードがあるとします。

  public async checkKoutoubu(input: string): Promise<string> {
if (input === '後頭部') {
return 'できものがありますね。痛そうです。';
} else {
throw new Error('後頭部以外は管轄外です!!!');
}
}

public async findDekimono() {
const result = await this.checkKoutoubu('上腕二頭筋').catch((e) => {
console.log(`後頭部チェックに失敗しました:${e.message}`);
});
console.log(result.replace('できもの', 'デキモノ'));
}

後頭部にできものがあるかどうかを確認し、表示するコードです。
表示する際に「できもの」をカタカナにreplaceして表示します。ありふれたコードですね。

さて、このコードですか、最終行のresult.replace('できもの', 'デキモノ')の部分でコンパイルエラーになります。
checkKoutoubu()メソッドの返り値はstringなのに、どうしてreplaceが処理できないのでしょうか。
(checkKoutoubu()に’上腕二頭筋’を渡しているからではありません。仮に’後頭部’を渡したとしても、全く同じコンパイルエラーになります。)

原因

理由は、result変数がstringとvoidのユニオン型になっているからです。
result変数がvoidの可能性があり、string型で確定できないため、本来stringが持つメソッドが呼び出せないのです。
ではでは、なぜvoidとのユニオン型になってしまっているのでしょうか。

上記のコードでは、checkKoutoubu()のメソッドチェーンでcatchを呼んでおり、その結果をresult変数に格納しているため、実際にはこの変数にはcheckKoutoubu()メソッドの返り値ではなくcatch()の返り値が格納されることになります。

これが、今回のメインのお話であるcatchの返り値問題になります。
ズバリ、catchの返り値というのは、対象の非同期メソッドの返り値とcatchに渡したコールバックメソッドの返り値のユニオン型になるのです。

上記のコードの場合、catchに渡したコールバックメソッドで返り値を設定していなかったため、voidとのユニオン型になってしまっていたんですね。

解決策

上記コードでコンパイルエラーが出ないようにするためには、catchのコールバックメソッドを以下のように修正する必要があります。

    const result = await this.checkKoutoubu('上腕二頭筋').catch((e) => {
console.log(`後頭部チェックに失敗しました:${e.message}`);
return '後頭部診てもらえなかった';
});

このように、対象の非同期メソッドと同じ型のものを返すようにしてあげれば、(この例でいうと) string | string のユニオン型 = sring型確定 となり、stringが持つメソッドが呼び出せるようになります。

しかし!
この解決策はベストではないでしょう。

これではcheckKoutoubu()メソッドの処理が失敗した場合でも成功した時と同じように以降の処理に進んでしまい、思わぬ不具合に繋がりかねません。
‘後頭部診てもらえなかった’という文字列は以降の処理では想定されていないはずですよね。

というわけで、実際には以下のような対応がいいのかなと思います。

  public async checkKoutoubu(input: string): Promise<string> {
if (input === '後頭部') {
return 'できものがありますね。痛そうです。';
} else {
throw new Error('後頭部以外は管轄外です!!!');
}
}

public async findDekimono() {
const result = await this.checkKoutoubu('上腕二頭筋').catch((e) => {
console.log(`後頭部チェックに失敗しました:${e.message}`);
return undefined;
});
if (!result) {
console.log('できもの捜索中に想定外のエラーが発生したので中止します');
return;
}
console.log(result.replace('できもの', 'デキモノ'));
}

これで、checkKoutoubu()でエラーが発生した場合も、安全に処理を終了させることができます。

ただ、なんとなくfindDekimono()メソッドでは例外投げない想定で書いていますが、この中でも例外を投げていいのであれば以下のような対応でもいいと思います。

  public async checkKoutoubu(input: string): Promise<string> {
if (input === '後頭部') {
return 'できものがありますね。痛そうです。';
} else {
throw new Error('後頭部以外は管轄外です!!!');
}
}

public async findDekimono() {
const result = await this.checkKoutoubu('上腕二頭筋').catch((e) => {
console.log(`後頭部チェックに失敗しました:${e.message}`);
throw new Error('後頭部チェックに失敗しました');
});
console.log(result.replace('できもの', 'デキモノ'));
}

こちらはcatchの中で例外発生させることで処理をそれ以降に流さない作戦ですね。
こちらの方がすっきりしていますが、これはこれで何か問題を孕んでいそうな雰囲気もあり少し怖い気もします…

最後に

今回の問題は提示した解決策で解決はできると思いますが、この記事で一番メモとして残しておきたかったのは、
後頭部にできものができてしまうので枕はちゃんと洗いましょうということです。
catchの返り値は対象の非同期メソッドの返り値とcatchに渡したコールバックメソッドの返り値のユニオン型になるという部分です。

提示した解決策がベストであるかはよく分からないので、解決策の方はあくまで一案として参考にしていただければと思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です