JavaScriptのError handlingについて

# 初めに
今回はエラーハンドリングの基本概念と応用についてまとめていきたいと思います。

参考文章はこちらです。
[Error handling, "try...catch"](https://javascript.info/try-catch)
[Custom errors, extending Error](https://javascript.info/custom-errors)

# try...catch(...finally)
<details><summary>動作の仕方</summary><div>

- 実行は`try{}`から始まり、エラーがなければ`catch(err)`が無視されます。
- エラーがあれば即時に停止し、`catch(err)`に入ります。
- `finally{}`ではエラーあるなしに関わりなく必ず実行する内容です。
</div></details>
<details><summary>注意点</summary><div>

- 実行段階（runtime）に有効なコードだけ動作します。
    - 解析段階（parse-time）でエラーが出たらコード自体が実行不能になるからです。
    - [Compiler Theory](https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/scope%20%26%20closures/ch1.md#compiler-theory)
- `setTimeout()`のような非同期Web APIsが別のところで処理するので、外側にある`catch(err)`は関数の内側にあるエラーをキャッチできません。
    - [Asynchronous Callback](https://qiita.com/hu-yu/items/a712884dc8422854299c#asynchronous-callback)
</div></details>

## `throw` error message & rethrowing
```jsx
// let json = '{"age": 30}'
let json = '{"age": 30, "name": ""}'


try {
  // a
  let user = JSON.parse(json)
  if (!user.name) {
    throw new SyntaxError('Incomplete data: no name')
  }
  console.log(user.name)
} catch (err) {
  if (err instanceof SyntaxError && err.message.indexOf('no name')) {
    console.log(`JSON Error: ${err.message}`)
  } else {
    console.log(`${err.name}: ${err.message}`)
  }
}

// ReferenceError: a is not defined
// JSON Error: Incomplete data: no name
```
`instanceof`でインスタンスの検定や、メッセージのキーワードから特定してエラーの分類をやってみました。

↓は再スローのところで、外側からもう一度`try...catch`でエラーをキャッチしようとします。
```jsx
function readJsonData() {
  let json = '{"age": 30, "name": "Mick"}'

  try {
    let user = JSON.parse(json)
    if (!user.name) {
      throw new SyntaxError('Incomplete data: no name')
    }
    // if user.name error occurred, it will jump to catch, and never execute b()
    b()
  } catch (err) {
    if (err.name === 'SyntaxError' && err.message.indexOf('no name')) {
      console.log(`JSON Error: ${err.message}`)
    } else {
      throw err
    }
  }
}

// it is a double-check
try {
  readJsonData()
} catch (err) {
  console.log(`External catch: ${err}`)
}

// External catch: ReferenceError: b is not defined
```
でも実際やってみたら、このやり方はダブルチェックみたいな行いだと感じています。なぜなら`try{}`で複数のエラーが出ても最初だけ反応してすぐ`catch(err){}`に入ります。
再スローは`try{}`も`catch(err){}`でのエラー分類も完璧だと思って、予期せぬエラー（分類のルートに入っていない）がでたら外側で対応できる方法だと思います。

## ...finally{}
実行順：
エラーがない場合は、`try`　⇒　`finally`
エラーがある場合は、`try`（中断）　⇒　`catch`　⇒　`finally`
```jsx
// ues devTool
// prompt(message, default)
let num = +prompt('Enter a positive integer number?', 35)

let diff, result

function fib(n) {
  // if n is negative or not an integer
  if (n < 0 || Math.trunc(n) !== n) {
    throw new Error('Must not be negative, or an integer')
  }
  // if n <= 1 is true, return itself, or fib() recursion
  return n <= 1 ? n : fib(n - 1) + fib(n - 2)
}

let start = Date.now()

try {
  result = fib(num)
} catch (e) {
  result = 0
} finally {
  diff = Date.now() - start
}

console.log(result || 'error occurred')
console.log(`execute took ${diff / 1000}sec`)

// 35.1
// error occurred
// execute took 0sec

// 35
// 9227465
// execute took 0.138sec
```
`finally{}`は必ず実行するコードなので、`try...catch`構文で計測や最終の確認などよく使われているようです。実行する内容がなければ省略していいです。

## finally vs. return
`try...catch...finally`構文では`finally{}`は`try{}`よりも後ろにいると見えるが必ず実行するコードなので、また`return`は外側に戻る最後のステップとして`finally{}`よりも先に実行しません。（`try{}`で`return`よりも先にエラーが発見する場合は`return`は実行されず`catch(err){}`に入り、そして`finally{}`へ。）
```jsx
function func() {

  try {
    return 1;

  } catch (err) {
    /* ... */
  } finally {
    console.log('finally');
  }
}

console.log(func())
// finally
// 1
```
# Custom errors, extending Error
## Extending Error
ここからは組み込みメソッド`Error`へ拡張し、`Error`内部のプロパティを利用した派生クラスのインスタンスの使い方を紹介したいと思います。
```jsx
let json = `{"name": "Mick", "age": 30}`

class ValidationError extends Error {
  constructor(message) {
    // [[HomeObject]] => Error constructor(message)
    super(message)
    this.name = 'ValidationError'
  }
}

function test() {
  throw new ValidationError('Error Occurred!')
}

try {
  test()
} catch (err) {
  console.log(err.message)
  console.log(err.name)
}

// Error Occurred!
// ValidationError
```
`super(message)`で[[HomeObject]]の`Error`の`constructor(message)`へアクセスし、親である`Error`のプロパティを利用しながら、自分のクラスに`this.name`へ値を指定する。
それでインスタンスの`new ValidationError`はカスタムエラーを創り出します。

↓は使用例ですが。
```jsx
// Usage
function readUser(json) {
  let user = JSON.parse(json)

  if (!user.age) {
    throw new ValidationError('No field: age')
  }
  if (!user.name) {
    throw new ValidationError('No field: name')
  }
  return user
}

try {
  let user = readUser('{"age": 25}')
} catch (err) {
  if (err instanceof ValidationError) {
    console.log(`Invalid data: ${err.message}`)
  } else if (err instanceof SyntaxError) {
    console.log(`JSON Syntax Error: ${err.message}`)
  } else {
    throw err
  }
}

// Invalid data: No field: name
```
要するに`err`オブジェクトの`instanceof`検定で親であることでエラーの分類を行います。

## Further extending
さらなる派生クラスへ拡張したらどうなるでしょう。
```js
// further extending
class PropertyRequiredError extends ValidationError {
  constructor(property) {
    // [[HomeObject]] => ValidationError constructor(message) => Error constructor(message)
    super(`No property: ${property}`)
    this.name = 'PropertyRequiredError'
    this.property = property
  }
}

// Usage
function readUser(json) {
  let user = JSON.parse(json)

  if (!user.age) {
    throw new PropertyRequiredError('age')
  }
  if (!user.name) {
    throw new PropertyRequiredError('name')
  }
}
```
`class PropertyRequiredError`は先ほどと同じく`super()`で親の`Error`のプロパティへアクセスし`property`を`Error constructor(message)`の引数として入れて、自分のフィールドで新しいプロパティ`name`と`property`を創り出す。

`instanceof`演算子はインスタンスの管理にも使われており、下のように各エラーのルートに入ります。
```jsx
console.log(new PropertyRequiredError instanceof Error) // true
console.log(new PropertyRequiredError instanceof ValidationError) // true
console.log(new PropertyRequiredError instanceof PropertyRequiredError) // true

try {
  let user = readUser('{"age": 25}')
} catch (err) {
  if (err instanceof ValidationError) {
    console.log(`Invalid data: ${err.name}, ${err.message}`)
  } else if (err instanceof SyntaxError) {
    console.log(`JSON Syntax Error: ${err.message}`)
  } else {
    throw err
  }
}

// Invalid data: PropertyRequiredError, No property: name
```

## Wrapping exceptions
いくら万全だと思ってもアクシデントが発生するかもしれません。しかしエラーのチェックいちいちやると時間や手間がかかってしまう。したがってエラータイプの分類や管理のしかたが重要になります。

まずは概ねどんな段階でエラーが発生したのか把握するなら`ReadError`読み込みエラーとか設定して、
```jsx
// if error occurred at the phase of reading data, create the corresponding error message
class ReadError extends Error {
  // [[HomeObject]] => Error constructor(message)
  constructor(message, cause) {
    super(message)
    this.cause = cause
    this.name = 'ReadError'
  }
}
```
その下にはまた細かい分類があれば、前の例のようにバリデータクラスを作り、有効データであるか、そしてさらに派生クラスにデータのプロパティなど細部のチェックをします。
```jsx
class ValidationError extends Error {
  constructor(message) {
    // [[HomeObject]] => Error constructor(message)
    super(message)
    this.name = 'ValidationError'
  }
}

class PropertyRequiredError extends ValidationError {
  constructor(property) {
    // [[HomeObject]] => ValidationError constructor(message) => Error constructor(message)
    super(`No property: ${property}`)
    this.name = 'PropertyRequiredError'
    this.property = property
  }
}
```
`validateUser`関数で細かいチェックを実行させたり、`readUser`で全体的なチェック（データ構文上のエラーか、データ内部のプロパティが見つからないか）`try...catch`を入れます。
```jsx
// check json data and console the invalid property
function validateUser(user) {
  if (!user.age) {
    throw new PropertyRequiredError('age')
  }
  if (!user.name) {
    throw new PropertyRequiredError('name')
  }
}

// check json data by try...catch
function readUser(json) {
  let user

  // check1: the err is SyntaxError instance or not
  // 
  try {
    user = JSON.parse(json)
  } catch (err) {
    if (err instanceof SyntaxError) {
      throw new ReadError('Syntax Error', err)
    } else {
      throw err
    }
  }

  // check2: the err is ValidationError instance or not
  try {
    validateUser(user)
  } catch (err) {
    if (err instanceof ValidationError) {
      throw new ReadError('Validation Error', err)
    } else {
      throw err
    }
  }
}

try {
  readUser('{bad json}')
} catch (err) {
  // catch error throw from readUser()
  if (err instanceof ReadError) {
    console.log(err)
    console.log(`Original error: ${err.cause}`)
  } else {
    throw err
  }
}
// ReadError: Syntax Error
// at readUser ....
// Original error: SyntaxError: Unexpected token b in JSON at position 1
```
最後はもう一回`try...catch`で分類されたエラーのメッセージをコンソール、またはほかのエラーと予想外のエラーをキャッチします。

簡単にいうと、
**エラーがどの段階で出現　⇒　エラーの細かい分類　⇒　エラーメッセージの出力や再スローなど**。