简体   繁体   English

NodeJS捕获错误的最佳实践

[英]NodeJS best practices for catching errors

I'm starting out w/ NodeJS and Express. 我开始使用NodeJS和Express。 Coming from the other popular scripting languages and C++ background, asynchronously calling DB functions is a bit foreign. 来自其他流行的脚本语言和C ++背景,异步调用DB函数有点陌生。 I've sorted out a pattern, but I'm still curious about catching exceptions. 我已经整理出一个模式,但是我仍然对捕获异常感到好奇。 Below is my basic pattern. 下面是我的基本模式。

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');
};

Generally I have a lot of endpoints and callbacks. 通常,我有很多端点和回调。 I'm a bit lost on where I set up ta try/catch block to catch errors thrown in the callback though. 我对于在哪里设置ta try / catch块来捕获回调中引发的错误有些迷惑。 I've looked around for some suggestions, but a lot of them seem to be on the web framework (if any) being used. 我到处寻找一些建议,但是很多建议似乎都在使用Web框架(如果有)上。

When you throw in an asynchronous callback, the exception just goes back to the internals of the database event handler and there is NO way for you to ever catch or handle that exception. 当您引发异步回调时,该异常仅返回到数据库事件处理程序的内部,并且您无法捕获或处理该异常。 So, basically it does no good at all. 因此,基本上根本没有好处。 It will just cause you to abort the handling of that request and you will never send a response on that request. 这只会导致您中止对该请求的处理,并且永远不会发送对该请求的响应。

Basically, you have several choices for how to handle the error. 基本上,您有几种选择方式来处理错误。 You can handle it completely right in each endpoint and send some sort of error response. 您可以在每个端点中完全正确地处理它,并发送某种错误响应。

Send Response right at each point of error 在每个错误点发送响应

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

Forward on to centralized error handler 转发到集中式错误处理程序

Or, you can forward the error on to a centralized error handler by calling next(err) : 或者,您可以通过调用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)
});

See Nodejs handle unsupported URLs and request types for more info on the ways to have generalized error handlers in Express. 有关在Express中具有通用错误处理程序的方式的更多信息,请参见Nodejs处理不支持的URL和请求类型

Use promises to collect errors within each route 使用承诺收集每个路线中的错误

If you are using more involved sequences of asynchronous operations where you may have more than one async operation sequenced together, then it does get to be a pain to handle errors at every single async operation. 如果您正在使用更多的异步操作序列,其中可能有多个异步操作一起排序,那么在每个异步操作中处理错误确实很痛苦。 This is where using promises with all your async operations more easily allows all the errors to percolate up to one .catch() statement at the top level of each route. 这是在所有异步操作中更轻松地使用.catch() ,所有错误都可以渗透到每个路由的顶层的一个.catch()语句中。 You don't say what database you're using, but here's an idea what that looks like. 您没有说您正在使用什么数据库,但这是一个看起来像什么的想法。 The general idea is that you can write your code so that all promise rejections (eg errors) will propagate up to one central .catch() in each route handler and you can then call next(err) from that .catch() , sending the error to your centralized error handler. 一般想法是,您可以编写代码,以便所有承诺拒绝(例如错误)都将传播到每个路由处理程序中的一个中央.catch() ,然后可以从该.catch()调用next(err) .catch() ,发送错误到您的集中式错误处理程序。 Here's how that looks for a recent version of Mongoose (you didn't say which database you were using) with one database operation. 这是通过一个数据库操作查找最新版本的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)
});

And, here's what it looks like if you have more than one operation: 而且,如果您执行多个操作,则显示为以下内容:

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)
});

Since ES6 built in support for promises and ES7 will add support for async/await for asynchronous operations (which is based on promises) and all significant libraries that offer asynchronous operations have added or are adding support for promises, it is clear that promises are the future of the language for managing asynchronous operations. 由于ES6内置了对Promise的支持,而ES7将增加对异步操作的异步/等待的支持(基于Promise),并且所有提供异步操作的重要库都已添加或正在添加对Promise的支持,因此,很显然,promise是用于管理异步操作的语言的未来。 That would be my strong recommendation. 那将是我的强烈建议。

You should never, ever throw an error like that! 您永远都不会抛出这样的错误! :) The reason is that at some point your whole node app will just stop working, because of some db query failed. :)原因是由于某些数据库查询失败,有时您的整个节点应用程序将停止运行。 This should be handled instead of just die. 这应该得到处理,而不仅仅是死掉。

And because this is a route handler - handles specific url that the user is getting (for example / ), there should be some output. 并且由于这是一个route处理程序-处理用户获取的特定URL(例如/ ),因此应该有一些输出。 You can always show a page with status 500 and a nice design, if there was such an error that you cannot handle or you might have your internal state messed up. 如果出现无法处理的错误或内部状态混乱,您总是可以显示状态为500且设计精美的页面。

So basically just act as nothing happened - return respones of any kind, or even render a page, but provide information that something went wrong. 因此,基本上什么也没做就可以-返回任何形式的respones ,甚至render页面,但提供发生错误的信息。

Also, a common scenario is something like what Alon Oz presented. 另外,常见的情况类似于Alon Oz提出的内容。 All routes in express are actually a middleware functions, that are called one after another. express中的所有路由实际上都是中间件功能,一个又一个地被调用。 If the route does not match the requested one, the function just skips and the next one is called. 如果路由与请求的路由不匹配,则该函数将跳过并调用下一个。 You can manually control that. 您可以手动控制它。 The actual pattern of the router is this: 路由器的实际模式是这样的:

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
});

The actual signature of next is next(err) . next的实际签名是next(err) So if you call it without any arguments, it will just skip to the next middleware. 因此,如果您不带任何参数调用它,它将仅跳至下一个中间件。 If you call it with an argument, it will skip all regular functions and go to the last ones in the stack, or more specifically the ones that handle errors. 如果使用参数调用它,它将跳过所有常规函数并转到堆栈中的最后一个函数,或更具体地说,将其处理错误。 They are like the regular ones, but taking four arguments instead of three: 它们就像常规参数一样, 但是采用四个参数,而不是三个:

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

It's very important to understand that this function will be called if you call next with an argument. 理解如果您接下来使用参数调用该函数将非常重要 Throwing an error won't do any good! 抛出错误不会有任何好处! Of course if none of your routes match the specific criteria (url) the final one will in the call will be called, so you can still handle the "not found" error. 当然,如果您的路由均不符合特定条件(url),则将调用呼叫中的最后一个条件,因此您仍然可以处理“未找到”错误。

This is a common scenario that you will use: 这是您将使用的常见方案:

// 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: {}
    });
});

Hope that helps! 希望有帮助! :) :)

Since you are using express, it has its own way to handle exceptions, defined like this: 由于您使用的是express,因此它具有自己的异常处理方式,如下所示:

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

For more info: 有关更多信息:

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

There are most commonly three major types of errors that we need to take into account. 我们通常需要考虑三种最主要的错误类型。

  1. Promise failures (Any failures that come up during async/await or result of a promise in then/catch) 承诺失败(在异步/等待过程中出现的任何失败,或随后/捕获中的承诺结果)

In order to handle promise failures, as suggested in the strong loop document or node js 2018 best practices , it's important to have a common function that can handle it. 如强循环文档或node js 2018最佳实践中所建议,为了处理承诺失败,重要的是要有一个通用的函数来处理它。

// 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. Unhandled Rejections 未处理的拒绝

    process.on('unhandledRejection', e => { // do something }); process.on('unhandledRejection',e => {//做某事});

  2. Unhandled exceptions 未处理的异常

    process.on('uncaughtException', e => { // do something }); process.on('uncaughtException',e => {//做某事});

If you see a lot of try/ catch blocks in your express methods you can abstract that to a separate async function like below: 如果您在express方法中看到很多try / catch块,则可以将其抽象为一个单独的异步函数,如下所示:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM