3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Expressのエラー処理まとめ 【第1弾】Expressのエラーハンミドラの定義

Last updated at Posted at 2022-10-17

はじめに

Expressを学び始めて、エラーハンドラの扱い方にいくつかのパターンがあるため、実装方法を整理。
本記事では、第1弾としてExpressにおける、基本的なエラーハンドラの使い方をまとめる。

  1. Expressのエラー処理まとめ 【第1弾】Expressのエラーハンミドラの定義(本記事)
  2. Expressのエラー処理まとめ 【第2弾】バリデーションのミドルウェア
  3. Expressのエラー処理まとめ 【第3弾】非同期関数のエラーハンドラ

1.Expressエラーハンドラの定義方法

エラー処理ミドルウェア関数の定義方法は、ほかのミドルウェア関数と引数の数が異なる。4つ(err、req、res、next)の引数を持ち、第1引数にエラーのオブジェクトを受け取る。また、エラーハンドラは基本的に最後に記述する。

// エラーが発生するルートハンドラ
app.get('/', (req, res, next) => {
    hoge.moge; //エラー発生
    res.send('エラーが発生しました。')
});

// エラーハンドラー
app.use((err, req, res, next) => {
    res.status(500).send(`エラーメッセージ:${err.message}`);
});

処理結果、関数hogeが見つからない、エラーをキャッチできていることが確認できる。
image.png

2.エラーハンドラへの飛ばし方

処理の途中のミドルウェア内で何らかのエラーを判断して、エラーハンドラへ処理を渡したい場合は、next(err) ※errは何らかの値の引数 を記述することで、後続に続くルーティングとミドルウェア関数をスキップして、エラーハンドラーへ処理を移す。

// エラーが発生するルートハンドラ
app.get('/error', (req, res, next) => {
    next('エラーが発生しました');
});

app.use((req, res) => {
    res.send('エラー時にスルーされるミドウェア')
})

// エラーハンドラー最後に定義
app.use((err, req, res, next) => {
    res.status(500).send(err);
});

処理結果、2つ目のミドルウェアがスキップされ、エラーハンドラへ処理が移る。
image.png

3.自作エラークラスの作成

Express標準のエラークラスでは、エラーコードを指定してレスポンスする場合、明示的に「res.status(999※任意のエラーコード)」のように、記述する必要がある。レスポンスの都度、res.status(999)の記述するとエラーコードの指定漏れが発生しやすくなる。そのため、エラークラスを自作してエラーメッセージとエラーコードを合わせたエラーオブジェクトを生成するクラスを定義することで、エラコードの指定漏れ対策を行うことが推奨されている。

//自作エラークラスの定義
class AppError extends Error { //標準のエラーハンドラーを継承
    constructor(message, status) {
        super(); //親クラスのコンストラクター呼び出し
        this.message = message; //引数で受け取ったメッセージをセット
        this.status = status; //引数で受け取ったエラーコードをセット
    }
};

// 権限エラーが発生するルートハドラ
app.get('/login', (req, res, next) => {
    //エラーオブジェクトを生成して、エラーハンドラへ処理を移す
    next(new AppError('アクセス権限がありません', 403)); 
});

//エラーハンドラー
app.use((err, req, res, next) => {
    const { status = 500, message = '何かエラーが起きました' } = err;
    res.status(status).send(message);
});

処理結果、エラー発生時に生成したエラーオブジェクトに保持するエラーコードがレスポンスされる。
image.png

基本的なエラーハンドラの定義方法はここまで、次は調べていて、なかなか解決できなかった疑問点を補足で記載。

4.「Next(err)」と「Throw new err」の違い

いろいろ調べていると、エラーを「Throw new err」で明示的に発生させてエラーハンドラへ飛ばすことができる、という内容を見つけた。ということで「3.自作エラークラスの作成」のコードを、「Next(new err)」から 「Throw new err」に書き換えて処理してエラーを発生させてみる。

//自作エラークラスの定義
class AppError extends Error { //標準のエラーハンドラーを継承
    constructor(message, status) {
        super(); //親クラスのコンストラクター呼び出し
        this.message = message; //引数で受け取ったメッセージをセット
        this.status = status; //引数で受け取ったエラーコードをセット
    }
};

// 権限エラーが発生するルートハドラ
app.get('/login', (req, res, next) => {
    throw new AppError('アクセス権限がありません', 403); //nextからthrowに書き換えてみる
    // next(new AppError('アクセス権限がありません', 403));
});


//エラーハンドラー
app.use((err, req, res, next) => {
    const { status = 500, message = '何かエラーが起きました' } = err;
    res.status(status).send(message);
});

処理結果は、「エラーオブジェクト生成」→「エラーハンドラの処理」の流れで3.の時と同じ。
ここで疑問。はて、「next(err)」と「throw new err」の違いは何なのかと。
image.png

いろいろ調べてみると、2つの違いは以下の通り。

  • Next(new err):nextを呼び出した時点で、エラーオブジェクトを生成しエラーハンドラーへ処理が移る。エラーハンドラーに遷移する即時性があると言える。
  • Throw new err:try-catchの中で使うことで、エラー処理がcatchの中に移る。(ただし、try-catchの構文外だと、エラーハンドラーへ処理が移る)エラーハンドラーへ移るまでに段階性があるといえる。

「Throw new err」について、try-catch構文をネストさせて、段階性を試してみる。

// 権限エラーが発生するルートハドラ(tray-catch ネスト)
app.get('/login', (req, res, next) => {
    try {
        try {
            try {
                throw new AppError('アクセス権限がありません', 403);
            } catch (e) {
                e.message += '(追記1)エラーをキャッチしました。'
                throw new AppError(e.message, 403);
            }
        } catch (e) {
            e.message += '(追記2)エラーをキャッチしました。'
            throw new AppError(e.message, 403);
        }
    } catch (e) {
        e.message += '(追記3)エラーをキャッチしました。'
        next(e);
    }

//エラーハンドラー
app.use((err, req, res, next) => {
    const { status = 500, message = '何かエラーが起きました' } = err;
    res.status(status).send(message);
});

処理結果は、確かに段階的にエラー処理が実施されていることがわかる。
image.png

つまり処理の流れの結論としては以下の形。

  • Next(err):「エラー発生」→「エラーオブジェクトの生成」→「エラーハンドラーで処理」となり、エラー直後にエラーハンドラーへ飛ばしたいときに有効
  • Throw new err:「エラー発生」→「(N階層目try-cathの)エラーオブジェクトの生成」→・・・→「(1階層目try-cathの)エラーオブジェクトの生成」→「エラーハンドラーで処理」 ※try-catch構文で括られていない場合は、「next(err)」と同じ処理の流れ。エラー発生時にその関数内で独自のエラー処理を行いたいときに有効

まとめ

Expressのエラーハンドラーについて、基本的な動き方を整理し、一般的なエラークラスの定義の方法についても理解することができた。nextとthrowの動作の違いについては理解できたが、nextとthrowの動きが一緒だったり異なったりする理由については、Expressの内部まで調べないといけなそうなので、一旦ここまで。(Express全体をtry-catchの大枠と捉えるとtryのネストとの関係性がイメージできそうであるが)自身のレベルが上がってきたら立ち戻って調べてみようと思う。
エラー処理のまとめ第2弾へ続く。

3
0
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
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?