TypescriptやJavaScriptで開発をしていると、async awaitをよく使います。コールバック関数のネストを防ぎ、コードのリーダビリティを保ちながらコードを書くことが出来るため有用なシンタックスです。
awaitの注意点
async functionでは、awaitを使う事ができるようになりますが、非同期処理内でエラーが発生した場合にキャッチする方法がないことに注意が必要です。
const func = async () => {
const response = await axios.get('http://....');
console.log('エラーが発生するとここが実行されない');
}
axios内で通信エラーが発生した場合は、例外エラーが発生し、それ以降の処理が実行されません。例外エラーを処理したい場合は、axiosの返すPromiseで.catchを使うしかありません。
const func = async () => {
const response = await axios.get('http://....')
.catch( error => error);
}
このようにすることで、例外エラーがスローされることなく、responseにエラーが返ります。
しかし、この時にresponseの型は AxiosResponse | AxiosError となり、responseにどちらの型が含まれているのかわからなくなってしまいます。
厳密には各型に含まれるフィールドから判定すれば良いのですが、型を推定するにはasを使う必要があり、コード量が増えてしまいます。かといって、.catch内で書いてしまうと、またコールバックのネストになってしまいます。
ts-resultsでラップする
ts-results
https://github.com/vultix/ts-results
ts-resultsは、RustにあるOption型やRusult型をTypeScriptで実装したもので、コンパイルタイムで型チェックの機能を持ちます。
import { Ok, Err, Result } from 'ts-results';
const func = async () => {
const result = await axios.get('http://....')
.then( res => Ok(res))
.catch( error => Err(error));
}
この場合、resultには
Result<AxiosResponse, AxiosError>
という型の値が返ります。
左の型が、Ok型となり、右の型がErrの型となります。
左と右は他の様々な型にすることが出来ます。
OkまたはErrどちらかの状態を持つことが出来るのがこのResult型となります。
エラーかどうか判定する
Result型は、簡単にエラーかどうか判定する事が出来ます。
import { Ok, Err, Result } from 'ts-results';
const func = async () => {
const result = await axios.get('http://....')
.then( res => Ok(res))
.catch( error => Err(error));
}
if (result.err) {
return result;
}
エラーがある場合は.err、エラーがない場合は.okにtrueが入ります。これによって、エラーがあった場合は処理を進めずに例外エラーを発生させることもなくResult型をそのまま返すことも出来ます。
値を取得する
特にエラーもなく、その後の処理を行いたい場合は
result.val
こちらを使用出来ますが、型の推定がうまくいかない場合があるので、.unwrapを使用します。
import { Ok, Err, Result } from 'ts-results';
const func = async () => {
const result = await axios.get('http://....')
.then( res => Ok(res))
.catch( error => Err(error));
}
if (result.err) {
return result;
}
const response = result.unwrap();
unwrapは、Result型からokだった場合に値を返します。errだった場合は、エラーになるので、あらかじめerrが入っていないか確認する必要があります。
このように、async awaitにts-resultsを使うことによって、安全にシステムを開発する事が出来ます。