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


# はじめに

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

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

# TL;DR

これが

```js
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)
}
```

こうなります

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

console.log('res is', response)
```

# まずは同期処理から

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

```js
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1748702/23827210-6ce4-e0b6-b59a-26ccbcdf8efc.png)

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

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

```js
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1748702/047b5d75-188f-323a-2b64-0ab2e6cdd3fa.png)

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

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

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

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


----

### throw を Promise.reject にする

```js
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()` にはいります。

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

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

```js
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` を変換する

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

// ↓ finally を変換する ↓

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

# 変換した結果

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

変換前

```js
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)
}
```

変換後

```js
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)
```

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

# 締め

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

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/await