カスタムエラークラスを定義して、FetchAPIのエラーハンドリングをする

最近 React x Redux x FetchAPI を実運用していて，APIリクエスト処理やエラーハンドリングが煩雑になりがちだったので，独自のエラークラスを定義して実装しやすくしてみた．
なおサンプルコードは React x Redux 以外のスタックでも使える．

# FetchAPIを扱うクラス

基本的なFetchAPIの解説は [お疲れさまXMLHttpRequest、こんにちはfetch](http://qiita.com/tomoyukilabs/items/9b464c53450acc0b9574) などを参考にした

```javascript:./src/API.js
export function send (url, method, body) {
  const headers = new Headers({
    'Content-Type': 'application/json'
  })

  const options = {
    method: method,
    headers: headers,
    mode: 'cors',
    credentials: 'include'
  }

  if (body) {
    options['body'] = JSON.stringify(body)
  }

  const request = new Request(url, options)

  return fetch(request).then(response => {
    // FetchAPIは通信エラーでない限り `catch` されないので，ここでハンドルする．
    if (!response.ok) {
      // TODO ここでエラーハンドリングをする
    }
    return response.json()
  })
}

export const GET = 'GET'
export const POST = 'POST'
export const PUT = 'PUT'
export const PATCH = 'PATCH'
export const DELETE = 'DELETE'
```

# カスタムエラークラス

JavaScript標準の [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) クラスだとテキストメッセージしか保持できないので，Fetch時の独自エラー情報を保持するCustomErrorクラスを定義する．

```javascript:./src/ExtendableError.js
'use strict'

export default class ExtendableError extends Error {
  constructor (msg, extra) {
    super(msg)
    this.name = this.constructor.name
    this.msg = msg
    this.extra = extra
    if (typeof Error.captureStackTrace === 'function') {
      Error.captureStackTrace(this, this.constructor)
    } else {
      this.stack = (new Error(msg)).stack
    }
  }
}
```

```javascript:./src/CustomError.js
import ExtendableError from './ExtendableError'

export default class CustomError extends ExtendableError {
  constructor (msg, status, code) {
    super(msg)
    this.code = parseInt(code)
    this.status = status
  }
}
```

## Babel6 つかっている場合

instanceof で型判定したとき正常動作しないケースがある．
Babel で Array と Error の extend がサポートされていないのが原因と思われる．
[Why doesn't instanceof work on instances of Error subclasses under babel-node?](https://stackoverflow.com/a/33877501/4535592)

そのため下記対応をした．

```javascript:./src/ExtendableError.js

const ExtendableBuiltin = cls => {
  function ExtendableBuiltin () {
    cls.apply(this, arguments)
  }

  ExtendableBuiltin.prototype = Object.create(cls.prototype)
  Object.setPrototypeOf(ExtendableBuiltin, cls)

  return ExtendableBuiltin
}

export default ExtendableBuiltin(ExtendableError)
```

または [babel-plugin-transform-builtin-extend](https://github.com/loganfsmyth/babel-plugin-transform-builtin-extend) を使って `Error` `Array` に対応させることもできる．

# エラーハンドリング例

API.js に APIError クラスを適用する．
エラーを catch する箇所では， `CustomError` のエラーかどうかを判定し処理を分岐する．

```javascript:./src/API.js
// 追記
import APIError from './APIError'

export function send (url, method, body) {
  const headers = new Headers({
    'Content-Type': 'application/json'
  })

  const options = {
    method: method,
    headers: headers,
    mode: 'cors',
    credentials: 'include'
  }

  if (body) {
    options['body'] = JSON.stringify(body)
  }

  const request = new Request(url, options)

  return fetch(request).then(response => {
    if (!response.ok) {
　　　 // 追記
      return response.json().then((e) => {
        throw new APIError(e.message, e.status, e.code)
      })
    }
    return response.json()
  })
}
```

serverのレスポンスJSONに `message` `status` `code` が含まれるケースを想定する．

```javascript:index.js
import * as API from './API'
import CustomError from './CustomError'

API.send(`/path/to/get/data`, API.GET)
    .then((response) => {
        if(!response.ok) {
            return resopnse.json().then((error) => {
                // {"status":400,"code":1000,"message":"セッションが無効です"}
                throw new CustomError(error.message, error.status, error.code)
            }
        }
        return response.json()
    }
    .catch((error) => {
        if(error instanceof CustomError) {
            console.error(`CustomError | message:${error.message}, status:${error.status}, code:${error.code}`)
        } else {
            console.error(`Normal Error | message:${error.message}`)
        }
    })
```

# Demo
TODO

# 参考
- [お疲れさまXMLHttpRequest、こんにちはfetch](http://qiita.com/tomoyukilabs/items/9b464c53450acc0b9574)
- [Why doesn't instanceof work on instances of Error subclasses under babel-node?](https://stackoverflow.com/a/33877501/4535592)