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を使うことによって、安全にシステムを開発する事が出来ます。