質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.53%

  • JavaScript

    16322questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

JavaScriptの例外処理の実用について

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 7
  • VIEW 928

hojo

score 178

async/awaitが利用できるようになって非同期処理の例外もキャッチできるようになりましたが、それなりの規模のアプリケーションを作成するにあたり、どのように例外処理を行えばよいか迷っています。

例えば以下はKoaを利用したウェブアプリケーションの例になりますが、catchで受け取ったエラーオブジェクトを判定してエラーの種類によって適切な処理に振り分けるようなことをするのが一般的だと認識しています。

const Koa = require('koa')
const app = new Koa()

app.use(async (ctx, next) => {

  try {
    await next()

  } catch(e) {

    // エラーインスタンスの種類を判定して適切に処理
    if (e instanceof TypeError ||
        e instanceof RangeError ||
        e instanceof EvalError) {
      ctx.state = 500
    }
  }
})


上記のように標準のエラーを振り分ける場合はこれでよいと思っているのですが「アクセス権限が無い」といったエラーを独自に作成する場合には以下のようにする必要があると思います。

const Koa = require('koa')
const app = new Koa()

app.use(async (ctx, next) => {

  try {
    await next()

  } catch(e) {

    // 標準エラーを適切に処理
    if (e instanceof TypeError ||
        e instanceof RangeError ||
        e instanceof EvalError) {
      ctx.state = 500

    // 認証エラーを適切に処理
    } else if (e instanceof AuthError) {
      ctx.state = 403
    }
  }
})

// --------------------------------

const AuthError = require('AuthError')

app.use((ctx, next) => {
  ...
  if( !isAdmin ) throw new AuthError()
})

しかし私はこのようにエラー処理を行っている例を見たことが無いので、本当にこれでよいのか不安です。

調べてみたところほとんどのサンプルではエラーを以下のように投げていました。

throw new Error('auth error')

しかしこうしてしまうとキャッチしたエラーを種類別に判定することができなくなってしまいますよね?

もちろんe.messageの内容を利用することでエラー別に適切な処理を行うことは不可能では無いと思っていますが、エラーメッセージは同じエラーでも内容が変わってしまうことがあると思うんです。

// 1234の値が状況によって変わるのでe.massegeをエラー種類判定に使えない
console.log(e.message) // "user 1234 is not found"

そのため、エラーの種類ごとに新しいエラークラスを作成してそのインスタンスを投げる方法しかないのだろうと思っているのですが、正直なところ大量のエラークラスを作成するのは面倒な作業だなと思っています。そこで、以下のように処理してしまえばいいのでは?と思いました。

const Koa = require('koa')
const app = new Koa()

app.use(async (ctx, next) => {

  try {
    await next()

  } catch(e) {

    // 標準エラーを適切に処理
    if (e.name == 'TypeError' ||
        e.name == 'RangeError' ||
        e.name == 'EvalError') {
      ctx.state = 500

    // 認証エラーを適切に処理
    } else if (e.name == 'AuthError') {
      ctx.state = 403
    }
  }
})

// --------------------------------

app.use((ctx, next) => {
  ...
  if( !isAdmin ) {
    let e = new Error('auth error')
    e.name = 'AuthError'
    throw e
  }
})

上記の方法ではe.nameの内容を書き換える必要があるのでカスマムエラークラスを作成していた時と比較して1行でエラーをthrowすることができていた部分が4行に膨れ上がっているため若干不満ですが、それ以外は満足のいくものになりました。

よし、これでいこう!と思ったのですが、本当にこれでいいのだろうか?皆様はどのように例外処理を行なっているのだろうということが気になって前に進むことができません。

本当にこのような方法で例外処理をしてもよいのでしょうか?

例えば、このようなe.nameを書き換える方法を使って例外を投げるモジュールを作成した場合、そのモジュールが、他のプロジェクトで利用することになった場合に問題は発生しないのだろうか?という新たな疑問が発生しました。

そもそも、一般的に例外処理はどのように行えばよいのでしょうか?
検索したのですが、あまりにも記事が出てこないので質問させていただきました。

よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+2

Errorクラスを継承するのが一般的なやり方だと考えます。

そこら辺に転がってるコードですと、実用レベルの複雑なケースを考えてないので、単純にErrorクラスにメッセージを載せて使っているというだけだと思います。

// モジュール側
module.exports = {
    AuthError: class AuthError extends Error {},
};
// 呼び出し側
const { AuthError } = require("hoge/fuga/Error");

try{
    // 処理()
}
catch(err){
    if(err instanceof AuthError){
        // AuthErrorに関する例外処理
    }
}

ただし、babelを使ってES5に対応させる場合、Errorクラスからの継承をサポートしていないため、このコードを使う場合はES2015が使える環境でお願いします。

具体的にはNode.js v4以上ですね。
(async-awaitを使ってるようなので、恐らく環境はNode.js v7.6だと考えていますが。)


で、エラークラスをいちいち作るのが面倒とのことですが、これは甘んじて受け入れるしかないです。

一応、evalを用いた手抜き生成方法だけ紹介します。
あるいは、プリプロセス処理を挟んで、コンパイル時に似たような方法でファイルを生成しても良いかもしれません。ここらへんは工夫して手を抜きましょう。

// モジュール側
const errorList = ["Auth", "Unknown", "Fatal"];

module.exports = errorList.reduce((obj, name)=> {
    obj[name+"Error"] = createErrorClass(name);
    return obj;
}, {});

function createErrorClass(name){
    return eval(`class ${name}Error extends Error {}`);
}

以下、参考程度に一般的ではない解決方法を紹介します。Symbol.forを用いた解決方法です。

メリットは

  • いちいちエラークラスを作る必要が無いのでとにかく楽な点
  • Babelをせざるを得ない場面に遭遇した時、Errorクラス継承とは異なり比較的導入しやすい

であり、デメリットは

  • 継承が出来ないため、instanceofを使って賢く処理する時につらい
  • e.messageに相当するものを添えることが出来ない
  • 一般的な解決方法ではない

です。

try{
    throw Symbol.for("hoge.fuga.AuthError");
}
catch(err){
    console.log(err instanceof Error);                      // false
    console.log(err === Symbol("hoge.fuga.AuthError"));     // false
    console.log(err === Symbol.for("hoge.fuga.AuthError")); // true
}

そもそも論ですが、今回やりたいことは例外を作ることではなく、例外を投げて種類に応じて処理を分類することですよね。
そういう場合には、わざわざErrorを投げる必要はなくて、種類さえわかればSymbolでもなんでも良いんですよね。そういう時はSymbolは他の言語のEnum相当の存在であり、相性が良かったりします。

今回はSymbol.forを用いたグローバルシンボルを利用しましたが、もちろんローカルシンボルを用いても可能です。好みで使い分ければ良いと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/24 08:23

    なるほど、丁寧な回答ありがとうございました。

    追加質問で申し訳ないのですが、このようなモジュールを見つけたのが、これを使うことについてはどう思われますでしょうか?https://github.com/yanickrochon/error-factory

    キャンセル

  • 2017/03/24 15:34

    初めて知りましたけど、ソースコードをざっと読む感じだと普通に良いモジュールなのかな、という印象を受けました。

    一方で、数年単位でリポジトリが放置されているのが気になります。
    この場合、何らかの不具合が判明した時にも、メンテナが不在でPRを飛ばしても無視される可能性があります。

    リスクを承知の上で採用する分には特に言うところはないので、後はご自身で判断されてみたら良いと考えます。

    キャンセル

  • 2017/03/24 15:56

    何から何までありがとうございましたm(_ _)m

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.53%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • JavaScript

    16322questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。