1030
712

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

async関数においてtry/catchではなくawait/catchパターンを活用する

Last updated at Posted at 2017-08-27

ご存知の通り、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関数において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の方が見やすいし好きなんでツライ

CiYnrgcVEAEk1Kk.jpg

完全に気持ちを代弁する画像をお借りしました。

1030
712
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1030
712

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?