最近 React x Redux x FetchAPI を実運用していて,APIリクエスト処理やエラーハンドリングが煩雑になりがちだったので,独自のエラークラスを定義して実装しやすくしてみた.
なおサンプルコードは React x Redux 以外のスタックでも使える.
FetchAPIを扱うクラス
基本的なFetchAPIの解説は お疲れさまXMLHttpRequest、こんにちはfetch などを参考にした
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クラスを定義する.
'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
}
}
}
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?
そのため下記対応をした.
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
のエラーかどうかを判定し処理を分岐する.
// 追記
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
が含まれるケースを想定する.
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