NodeJS捕获错误的最佳实践

时间:2017-03-05 17:37:00

标签: node.js express error-handling

我开始使用NodeJS和Express。来自其他流行的脚本语言和C ++背景,异步调用数据库函数有点陌生。我已经整理出一个模式,但我仍然对捕获异常感到好奇。以下是我的基本模式。

var callback = function(req, res) {
    // do stuff
    connection.query(queryString, function(err,result){
        if (err) throw err;
        // process results.
    };
};

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

app.get('/', callback);
app.listen(3000,function() {
    console.log('listening');
};

通常我有很多端点和回调。我在设置ta try / catch块的地方有点迷失,以捕获回调中抛出的错误。我四处寻找一些建议,但其中很多似乎都在使用网页框架(如果有的话)。

4 个答案:

答案 0 :(得分:8)

当您引入异步回调时,异常只会返回到数据库事件处理程序的内部,并且您无法捕获或处理该异常。所以,基本上它根本没有好处。它只会导致您中止对该请求的处理,并且您永远不会对该请求发送响应。

基本上,您有多种选择来处理错误。您可以在每个端点完全处理它并发送某种错误响应。

在每个错误点发送响应

app.get('/', function(req, res) {
    // do stuff
    connection.query(queryString, function(err,result){
        if (err) return res.status(500).send(someErrorResponse);
        // process results.
    };
});

转发到集中式错误处理程序

或者,您可以通过调用next(err)

将错误转发到集中式错误处理程序
app.get('/', function(req, res, next) {
    // do stuff
    connection.query(queryString, function(err,result){
        // if error, forward it on to our centralized error handler
        if (err) return next(err);
        // process results.
    };
});

// centralized error handler - note how it has four parameters
app.use(function(err, req, res, next) {
    // formulate an error response here
    console.log(err);
    res.status(500).send(someErrorMessage)
});

有关如何在Express中使用通用错误处理程序的更多信息,请参阅Nodejs handle unsupported URLs and request types

使用promises收集每条路线中的错误

如果您正在使用更多涉及的异步操作序列,其中您可能有多个异步操作一起排序,那么在每个异步操作中处理错误确实很麻烦。这是使用所有异步操作的promises更容易允许所有错误在每个路由的顶层渗透最多一个.catch()语句的地方。你不会说你正在使用什么数据库,但是这里有一个想法。一般的想法是,您可以编写代码,以便所有承诺拒绝(例如错误)将在每个路由处理程序中传播到一个中心.catch(),然后您可以从next(err)调用.catch() },将错误发送到您的集中错误处理程序。以下是通过一次数据库操作查找最近版本的Mongoose(您没有说明您正在使用哪个数据库)的方法。

app.get('/', function(req, res, next) {
    // do stuff
    connection.query(queryString).exec().then(function(result){
        // process results.
    }).catch(next);
});

// centralized error handler - note how it has four parameters
app.use(function(err, req, res, next) {
    // formulate an error response here
    console.log(err);
    res.status(500).send(someErrorMessage)
});

而且,如果您有多个操作,这就是它的样子:

app.get('/', function(req, res, next) {
    // do stuff
    connection.query(queryString).exec().then(function(result){
        // process results, then make another query
        // return the promise from this second operaton so both results 
        // and error are chained to the first promise
        return connection.query(...).exec();
    }).then(function(result) {
        // process chained result
    }).catch(next);
});

// centralized error handler - note how it has four parameters
app.use(function(err, req, res, next) {
    // formulate an error response here
    console.log(err);
    res.status(500).send(someErrorMessage)
});

由于ES6内置支持promises,ES7将为异步操作(基于promises)添加异步/等待支持,并且所有提供异步操作的重要库都添加或正在添加对promises的支持,很明显promises是管理异步操作的语言的未来。那是我的强烈建议。

答案 1 :(得分:3)

你永远不应该抛出这样的错误! :)原因是在某些时候你的整个节点应用程序将停止工作,因为一些数据库查询失败。这应该被处理,而不是只是死。

因为这是一个route处理程序 - 处理用户正在获取的特定URL(例如/),所以应该有一些输出。您可以随时显示状态为500且设计不错的页面,如果出现您无法处理的错误,或者您的内部状态可能会混乱。

所以基本上只是表现一无所获 - 返回任何类型的respones,甚至是render页面,但提供出现问题的信息。

此外,常见的情况类似于Alon Oz所呈现的内容。 express中的所有路由实际上都是一个中间件函数,它们被称为一个接一个。如果路由与请求的路由不匹配,则该功能只是跳过并调用下一个。你可以手动控制它。路由器的实际模式如下:

app.get('/', function(req, res, next) {
    // you can have the request
    // you can send response like res.send('hello')
    // OR you can skip this function using NEXT
});

下一个的实际签名是next(err)。因此,如果您在没有任何参数的情况下调用它,它将跳转到下一个中​​间件。如果使用参数调用它,它将跳过所有常规函数并转到堆栈中的最后一个函数,或者更具体地说是处理错误的函数。他们就像常规的那样,取四个参数而不是三个:

app.use(function (err, req, res, next) { });

非常重要了解如果您使用参数调用,将会调用此函数。投掷错误不会有任何好处!当然,如果你的路线都没有符合特定的标准(url),那么呼叫中的最后一个将被调用,所以你仍然可以处理" not found"错误。

这是您将使用的常见方案:

// development error handler, will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        debug('ERROR [ip: %s]:: dev env -> ', req.ip, err); // I'm using debug library - very helpful
        res.status(err.status || 500);
        res.render('deverr', { // I render custom template with the whole stack beautifully displayed
            errMessage: err.message,
            error: err
        });
    });
}

// production error handler, no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('pages/error', { // custom error page with nice design and a message
        errMessage: err.message,
        error: {}
    });
});

希望有所帮助! :)

答案 2 :(得分:1)

由于您使用express,它有自己的方式来处理异常, 定义如下:

function clientErrorHandler (err, req, res, next) {
    if (req.xhr) {
        res.status(500).send({ error: 'Something failed!' })
    } else {
        next(err)
    }
}
app.use(clientErrorHandler)

欲了解更多信息:

https://expressjs.com/en/guide/error-handling.html

答案 3 :(得分:0)

我们通常需要考虑三种最主要的错误类型。

  1. 承诺失败(在异步/等待或在then / catch中出现承诺的任何失败)

如强循环文档或node js 2018 best practices中所述,为了处理承诺失败,重要的是要有一个通用的函数来处理它。

// app.js file

app.get('/:id', async (req,res,next) => {
    if(!req.params.id) {
         return res.status(412).send('enter a valid user id');
    }
    try {
       const results = await UserDAL(id);
    } catch(e) {
      next(e);
    }
}

// common error middleware defined in middleware/error.js

module.exports = function (err,req,res,next) {
     logger.error(`${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`);
     return res.status(500).send('something failed.');
};
  1. 未处理的拒绝

    process.on('unhandledRejection',e => {      // 做点什么 });

  2. 未处理的异常

    process.on('uncaughtException',e => {    // 做点什么 });

如果您在express方法中看到很多try / catch块,则可以将其抽象为一个单独的异步函数,如下所示:

module.exports = function asyncMiddleWare(handler) {
    return async (req,res,next) => {
        try {
            await handler(req,res)
        } catch(e) {
            next(e);
        }
    }
};