ご存知の通り、async/awaitはとても美しく明快であり、JS界隈の誰もが待ち望んだ機能であります。
async function f() {
const x = await g()
}
async/awaitのエラーハンドリングはtry/catchで行うのが一般的です。
しかし、これは複数のawaitを使い、それぞれ別のエラーハンドリングを行いたい場合など、冗長になりがちです。
そして、特に気に入らないのが、tryのスコープ外で非同期関数の戻り値を使う場合、letを使う必要があるところです。
let name = 'ゲスト'
try {
name = await getName()
} catch (err) { }
console.log(`ようこそ ${name}さん`)
そこで、await/catchパターンを活用して、これを改善してみましょう。
##await/catch パターン
上記をawait/catchパターンで書き換えてみます。
let name = 'ゲスト'
try {
name = await getName()
} catch (err) { }
console.log(`ようこそ ${name}さん`)
// => ようこそ ゲストさん
/// ↓ ↓ ↓
const name = await getName().catch(() => 'ゲスト')
console.log(`ようこそ ${name}さん`)
// => ようこそ ゲストさん
グレート!
あの忌々しいletが消え去り、constにより平和が訪れました。ああ、不変性の美しきかな。なにより、ネストが減るのがいいですね。
もう一つパターンを見てみましょう。
複数のasync関数のエラーをそれぞれ別々にハンドリングする必要ある場合です。
try {
await f()
} catch (err) {
handleErr1(err)
}
try {
await g()
} catch (err) {
handleErr2(err)
}
// ↓ ↓ ↓
await f().catch(handleErr1)
await g().catch(handleErr2)
完璧です。
完全に冗長な記述が消え、シンプルなコードになりました。
結局のところ、async関数はPromiseを返す関数でしかなく、awaitはPromiseの解決を待つので、catchをそのまま使えるわけです。
Tips
もちろんawaitとthenを組み合わせることができます。
これは、いちいち変数を用意するまでもない(むしろ冗長な)連続した処理に活用できるでしょう。
const x1 = await func()
const x2 = await filter1(x1)
const x3 = await filter2(x2)
// ↓ ↓ ↓
const x = await func().then(filter1).then(filter2)
まとめ
await catchパターンを活用することで、コードが簡潔になることがわかりました。
何かあれば、コメント欄またはツイッターにて議論しましょう。
async/awaitのcatchパターン。async関数はpromiseを返す関数でしかないので、catchをそのまま使える。 pic.twitter.com/cm3v9Ugj85
— 撃殺コスモ地獄拳 (@akameco) 2017年8月22日
APIの異常系をテストするときawait catchパターンを使うと便利 pic.twitter.com/wqb1aYWPAb
— 撃殺コスモ地獄拳 (@akameco) 2017年8月22日
本記事は、以下からの転載です。
async関数においてtry/catchではなくawait/catchパターンを活用する – 赤芽 – Medium
twitterおよびはてブのコメントの反映
情報の精度を上げるため、はてブやtwitterでの意見を出来る範囲で反映していきます。
💬 たまにやるけど、エラー握りつぶしながら動きそうで怖い from はてブ
try/catchでもcatch使う場合でも、エラーをハンドリングするわけですから、握りつぶしながら動くという表現はよくわかりませんが、catch内でErrorをthrowした場合の処理について見てみます。
function asyncFunc() {
return Promise.reject(new Error('throw from asyncFunc'))
}
async function main() {
try {
await asyncFunc().catch(err => {
console.log(err)
throw new Error('throw from await/catch')
})
} catch (err) {
console.log(err)
}
}
main()
// => throw from asyncFunc
// => throw from await/catch
catch内でthrowした場合、その上のtry/catchで補足できます。
Promise.rejectを使ってみます。
try {
await asyncFunc().catch(err => {
console.log(err)
return Promise.reject(new Error('throw from await/catch'))
})
} catch (err) {
console.log(err)
}
// => throw from asyncFunc
// => throw from await/catch
次は、catchのチェインについて見てみます。
await asyncFunc()
.catch(err => {
console.log(err.message)
return Promise.reject(new Error('throw from await/catch'))
})
.catch(err => {
console.log(err.message)
})
// => throw from asyncFunc
// => throw from await/catch
いずれのパターンもPromiseを扱い場合と同じようにハンドリングできるようです。
また、promiseのチェインは途中でcatchすることも可能なので、以下のような最後にthenを繋げる書き方も可能です。
const result = await asyncFunc()
.catch(err => {
console.log(err.message)
return Promise.reject(new Error('throw from await/catch'))
})
.catch(err => {
console.log(err.message)
})
.then(() => 1)
console.log(result)
// => throw from asyncFunc
// => throw from await/catch
// => 1
💬 確かに素晴らしいんだけど、constよりletの方が見やすいし好きなんでツライ
完全に気持ちを代弁する画像をお借りしました。