2012-12-07

[Express] エラーの伝搬と処理について

Express でリクエストを処理しているとき、発生したエラーを適切に処理する仕組みについてドキュメントではあまり詳しく取り扱われていませんが、少し理解しておくともっと楽しくコードが書けると思います。

express において設定するコールバック関数は受け取る引数の数で勝手に分類され、3個以下の場合は正常系、4個の場合は異常系となります。正常系のとき引数リストは req, res, next で、異常系のときは err, req, res, next です。

正確には connect の範疇となるミドルウェアも、express に内蔵されているリクエストルーターと同じような振る舞いをするように作られています。

正常系でエラーが発生した場合はコールバック関数と同期的に発生したエラーは throw でも良いですが非同期的に発生したエラーは next(err) として伝搬させる必要があります。区別が面倒な方はすべて next(err) と書くことにしてもよいと思います。

異常系のコールバック関数は必ずエラーに対処しないといけないわけではなく、next(err) を呼び出してエラーを伝搬することもできます。

全てのミドルウエアを処理してもエラーを処理しきれなかったとき connect によりデフォルトのエラー画面が表示されます。あらかじめミドルウェアの最後の方に errorHandler を入れておくと素敵なエラーページを出せます。connect のモジュールを使っているのにタイトルが Express となる理由は express の lib/express.js を見ると分かります。

以下のコードで色々試してみてください。

/*
Q. 以下のアドレスのレスポンスはどうなるでしょうか
http://localhost:3000/
http://localhost:3000/?error=1
http://localhost:3000/?error=1&catch=1
http://localhost:3000/?pass=1
*/

process.env.DEBUG = '*';

var express = require('express'),
    app = express();

// middleware
app.use(express.logger());
app.use(function middlewareA(req, res, next) {
    req.trace = [0];
    next();
});
app.use(app.router);
app.use(function middlewareB(err, req, res, next) {
    req.trace.push(5);
    res.send({msg:'final: error', trace: req.trace, error: String(err)});
});
app.use(function middlewareC(req, res, next) {
    req.trace.push(6);
    res.send({msg:'final: not error', trace: req.trace});
});

// request router
app.get('/', function (req, res, next) {
    req.trace.push(1);
    next();
});
app.get('/', function (req, res, next) {
    req.trace.push(2);

    if (req.param('error')) {
        next(new Error('something happened'));
    } else {
        next();
    }
}, function (req, res, next) {
    req.trace.push(3);

    if (req.param('pass')) {
        next();
    } else {
        res.send({msg:'ok', trace: req.trace});
    }
}, function (err, req, res, next) {
    req.trace.push(4);

    if (req.param('catch')) {
        res.send({msg:'caught error', trace: req.trace, error: String(err)});
    } else {
        next(err);
    }
});

app.listen(3000);

リンク
node.js - Error handling principles for NodeJS + Express apps? - Stack Overflow
express/test/app.routes.error.js