6
3

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 1 year has passed since last update.

【JavaScript】非同期のtry-catchをいい感じに書く

Last updated at Posted at 2022-03-22

はじめに

エラーハンドリングって大変ですよね。
非同期がはいってくるともっと大変になります。
さいきんそう思うことがあって調べていたら、いい感じの書き方を見つけました。

ここでは try-catch ではなくて await-catch を使って非同期のエラーハンドリングを書くことで、すっきりした書き方になることを目指します。

TL;DR

これが

try {
  const response = await callApi()
  apiSuccess = true
  console.log('res is', response)
} catch(e) {
  if (e instanceof TypeError) {
    console.log('handle type')
  } else if (e instanceof SyntaxError) {
    console.log('handle syntax')
  } else {
    throw e
  }
} finally {
  console.log('success?', apiSuccess)
}

こうなります

const response = await callApi()
  .then(() => { apiSuccess = true })
  .catch(handleType)
  .catch(handleSyntax)
  .catch(Promise.reject)
  .finally(() => {
    console.log('success?', apiSuccess)
  })

console.log('res is', response)

まずは同期処理から

通常のエラーハンドリングはこんな感じになるかと思います。
まずは同期処理ですが、以降の例でこれを変えていきます。

function callApi() {
 throw new SyntaxError('a')
}

let apiSuccess = false
try {
  const response = callApi()
  apiSuccess = true
  console.log('res is', response)
} catch(e) {
  if (e instanceof TypeError) {
    console.log('handle type')
  } else if (e instanceof SyntaxError) {
    console.log('handle syntax')
  } else {
    throw e
  }
} finally {
  console.log('success?', apiSuccess)
}

結果はこうなります。
image.png

API を呼ぶときは非同期にしたい

API は非同期で呼びたいですね。
なので非同期にします。

async function callApi() { // async にする
  throw new SyntaxError('a')
}

let apiSuccess = false
try {
  const response = await callApi() // await にする
  apiSuccess = true
  console.log('res is', response)
} catch(e) {
  if (e instanceof TypeError) {
    console.log('handle type')
  } else if (e instanceof SyntaxError) {
    console.log('handle syntax')
  } else {
    throw e
  }
} finally {
  console.log('success?', apiSuccess)
}

結果はこうなります。
image.png

await がないと catch されずにエラーが突き抜けるので注意です。

await の try-catch といえば(本題)

さて、 javascript の await はメソッドチェーンでエラーハンドルができます。
するとどうなるか。コードがすっきりして嬉しいです。

というわけで、同期処理の try-catch を非同期処理の書き方に変換します。
throw についてはこのままでも動きますが、await にあわせて Promise.reject を使います。


throw を Promise.reject にする

async function callApi() {
  throw new SyntaxError('a')
}

// throw を Promise.reject にする

async function callApi() {
  return Promise.reject(new SyntaxError('a'))
}

try を変換する

try の処理を、[ throw するもの] と [throw しないもの] と [戻り値を扱うもの] に分けます。
throw をするものはそのまま書いて、throw をしないものは .then() に書きます。
もし処理の中で throw されると .then() は無視して、直近の .catch() にはいります。

また、戻り値を扱う処理はメソッドチェーンのあとに書きます。

try {
  const response = await callApi()
  apiSuccess = true
  console.log('res is', response)
}

// ↓ try を消す ↓

const response = await callApi()      // throw するもの
  .then(() => { apiSuccess = true })  // throw しないもの

// 戻り値を扱うもの
console.log('res is', response)

catch を変換する

catch の中の処理を書き換えます。
メソッドチェーンにしたいので、エラーを引数にしたメソッドに切り出します。
今回の例は処理が複数あるので、メソッドを複数個つくっています。

catch(e) {
  if (e instanceof TypeError) {
    console.log('handle type')
  } else if (e instanceof SyntaxError) {
    console.log('handle syntax')
  } else {
    throw e
  }
}

// ↓ if (...) { ... } を非同期関数にする ↓

async function handleType(e) {
  if (e instanceof TypeError) {
    console.log('handle type')
  } else {
    return Promise.reject(e)
  }
}

async function handleSyntax(e) {
  if (e instanceof SyntaxError) {
    console.log('handle syntax')
  } else {
    return Promise.reject(e)
  }
}

// ↓ catch を消す ↓

.catch(handleType)
.catch(handleSyntax)
.catch(Promise.reject)

finally を変換する

finally {
  console.log('success?', apiSuccess)
}

// ↓ finally を変換する ↓

.finally(() => {
  console.log('success?', apiSuccess)
})

変換した結果

というわけで、まとめると以下のようになります。

変換前

async function callApi() {
  throw new SyntaxError('a')
}

let apiSuccess = false
try {
  const response = await callApi()
  apiSuccess = true
  console.log('res is', response)
} catch(e) {
  if (e instanceof TypeError) {
    console.log('handle type')
  } else if (e instanceof SyntaxError) {
    console.log('handle syntax')
  } else {
    throw e
  }
} finally {
  console.log('success?', apiSuccess)
}

変換後

async function handleType(e) {
  if (e instanceof TypeError) {
    console.log('handle type')
  } else {
    return Promise.reject(e)
  }
}

async function handleSyntax(e) {
  if (e instanceof SyntaxError) {
    console.log('handle syntax')
  } else {
    return Promise.reject(e)
  }
}

async function callApi() {
  return Promise.reject(new SyntaxError('a'))
}

let apiSuccess = false
const response = await callApi()
  .then(() => { apiSuccess = true })
  .catch(handleType)
  .catch(handleSyntax)
  .catch(Promise.reject)
  .finally(() => {
    console.log('success?', apiSuccess)
  })

console.log('res is', response)

記述量が増えた?その通りです。
ですが、見やすくなるので私はこちらの方が好きです。こっちのほうがスマートじゃない?
あと handleTypehandleSyntax は他のエラーハンドリングでも使いまわせるので、総合的には記述量が減ることを見込めます。

締め

こちらを参考に書きました
もっといい方法がありましたら教えてください。

6
3
1

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
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?