LoginSignup
8
11

More than 5 years have passed since last update.

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

Posted at

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

FetchAPIを扱うクラス

基本的なFetchAPIの解説は お疲れさまXMLHttpRequest、こんにちはfetch などを参考にした

./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 クラスだとテキストメッセージしか保持できないので,Fetch時の独自エラー情報を保持するCustomErrorクラスを定義する.

./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
    }
  }
}
./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?

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

./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 を使って Error Array に対応させることもできる.

エラーハンドリング例

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

./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 が含まれるケースを想定する.

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

参考

8
11
0

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
8
11