如何在node.js中处理代码异常?

28

我查阅了Express的文档,但是关于错误处理的部分对我来说完全不透明。

我猜到他们提到的app指的是一个createServer()实例,是吗?但我不知道如何停止node.js在处理请求时出现异常时导致应用程序崩溃。

其实我不需要什么花里胡哨的东西;我只想在出现异常时返回状态码500,以及一个否则为空的响应。节点进程不能因为某个地方有未捕获的异常而终止。

有没有简单的示例可以实现这个需求?


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

var app = express.createServer();

app.get('/', function(req, res){
    console.log("debug", "calling")
    var options = {
        host: 'www.google.com',
        port: 80,
        path: "/"
    };
    http.get(options, function(response) {
       response.on("data", function(chunk) {
           console.log("data: " + chunk);
           chunk.call(); // no such method; throws here
       });
    }).on('error', function(e) {
       console.log("error connecting" + e.message);
    });
});

app.configure(function(){
    app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.listen(3000);

导致应用程序崩溃,生成 traceback

mypath/tst.js:16
           chunk.call(); // no such method; throws here
                 ^ TypeError: Object ... has no method 'call'
    at IncomingMessage.<anonymous> (/Library/WebServer/Documents/discovery/tst.js:16:18)
    at IncomingMessage.emit (events.js:67:17)
    at HTTPParser.onBody (http.js:115:23)
    at Socket.ondata (http.js:1150:24)
    at TCP.onread (net.js:374:27)

如果某处出现未捕获的异常,该进程将会停止。如果您不希望在发生异常时终止进程,请捕获异常并返回500错误。 - Chad
3
你可能对不使用"express方式"感兴趣:https://dev59.com/SG855IYBdhLWcg3wq2TL - Matt
5个回答

47

如果你想捕获所有的异常并提供一些处理方法,而不是退出 Node.js 进程,你需要处理 Node 的 uncaughtException 事件。

思考一下,这是一个 Node 的问题,而不是 Express 的问题,因为如果你从某个任意的代码块中抛出异常,Express 并不能保证能够或者会看到它,或者有机会捕获它。为什么呢?因为异常在异步事件驱动的回调函数式的 Node 风格编码中无法很好地互动。异常沿着调用堆栈向上寻找一个在异常抛出时处于作用域内的 catch() 块。如果 myFunction 将一些工作延迟到运行某些事件发生时的回调函数中,然后返回到事件循环,那么当该回调函数被调用时,它将直接从主事件循环中调用,而 myFunction 就不再在调用堆栈上了;如果此回调函数抛出异常,即使 myFunction 有 try/catch 块,也无法捕获异常。

实际上这意味着,如果你在由 Express 直接调用的某个函数中抛出异常而没有自己捕获它,并且你做的是在响应某个异步事件的函数中抛出异常,Express 可以捕获异常并调用你安装的错误处理程序,假设你已经配置了一些错误处理中间件,例如 app.use(express.errorHandler())。但是如果你在响应异步事件的函数中间接地调用同样的异常,Express 就无法捕获它。(唯一可以捕获它的方法是监听全局 Node 的 uncaughtException 事件,这是一个坏主意,首先因为它是全局的,你可能需要将其用于其他事情,其次因为 Express 不知道与异常相关联的请求是什么。)

这是一个例子。我将这段路由处理代码添加到现有的 Express 应用程序中:

app.get('/fail/sync', function(req, res) {
   throw new Error('whoops');
});
app.get('/fail/async', function(req, res) {
   process.nextTick(function() {
      throw new Error('whoops');
   });
});
现在,如果我在浏览器中访问http://localhost:3000/fail/sync,浏览器将转储一个调用堆栈(展示了express.errorHandler的操作)。然而,如果我在浏览器中访问http://localhost:3000/fail/async,浏览器会变得很生气(Chrome会显示“未收到数据:错误324,net::ERR_EMPTY_RESPONSE:服务器在没有发送任何数据的情况下关闭了连接”的消息),因为Node进程已经退出,在调用它的终端窗口中显示了回溯。

6
这个回答非常好,因为它不仅清晰地阐述了Express如何捕获异常并使用错误中间件来处理它们(这是一种意外的默认行为),而且还解释了在异步函数中使用“throw new Error("...")”将不会触发错误中间件,甚至不会被任何包装异步函数的异常处理程序捕获。你在其他地方找不到这些信息。 - fthinker
2
使用是捕获“未捕获异常”的更好选择。 - James
2
域名正在被淘汰的过程中。有什么替代API吗? - mcont
一直在尝试抛出一个异常,但 Express 却没有捕获它。解释得非常清楚明白。 - Raf

9
为了能够捕获异步错误,我使用域(domain)。使用 Express 时,您可以尝试以下代码:
function domainWrapper() {
    return function (req, res, next) {
        var reqDomain = domain.create();
        reqDomain.add(req);
        reqDomain.add(res);

        res.on('close', function () {
            reqDomain.dispose();
        });
        reqDomain.on('error', function (err) {
            next(err);            
        });
        reqDomain.run(next)
    }
}
app.use(domainWrapper());
//all your other app.use
app.use(express.errorHandler());

这段代码将捕获您的异步错误并发送到错误处理程序。在此示例中,我使用express.errorHandler,但它适用于任何处理程序。

有关domain的更多信息:http://nodejs.org/api/domain.html


这里有一个npm包可以做到这一点:https://www.npmjs.com/package/express-domain-middleware - pjincz
最佳答案/解决方案! - felixfbecker
7
domain 模块即将被弃用,所以我不建议使用它。 - mauvm

2
您可以使用Express默认的错误处理程序,实际上它是Connect错误处理程序
var app = require('express').createServer();

app.get('/', function(req, res){
  throw new Error('Error thrown here!');
});

app.configure(function(){
    app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.listen(3000);

更新 对于你的代码,实际上需要捕获错误并像这样将其传递给express

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

var app = express.createServer();

app.get('/', function (req, res, next) {
  console.log("debug", "calling");
  var options = {
    host:'www.google.com',
    port:80,
    path:"/"
  };
  http.get(options,
    function (response) {
      response.on("data", function (chunk) {
        try {
          console.log("data: " + chunk);
          chunk.call(); // no such method; throws here

        }
        catch (err) {
          return next(err);
        }
      });
    }).on('error', function (e) {
      console.log("error connecting" + e.message);
    });
});

app.configure(function () {
  app.use(express.errorHandler({ dumpExceptions:true, showStack:true }));
});

app.listen(3000);

你的示例代码可以正常运行(在添加 var express = require('express'); 后),但是不幸的是,当我在我的代码中尝试相同的操作时,它仍然会导致进程崩溃。我已经更新了问题并提供了示例代码。 - user124114
我已经更新了答案,展示了如何捕获错误并使用next()传递给表达式。 - 250R
谢谢 @250R!不幸的是,我无法在代码逻辑的各个地方都使用这样的结构,所以我会选择Matt在上面评论中提到的解决方案。 - user124114
最近的node.js似乎使用了var errorhandler = require('errorhandler'); app.use(errorhandler()); - robocat
这个方法添加了太多的样板代码 :( - Danyal Aytekin

2
27天前,Express 5.0.0-alpha.7发布了。通过这个特定的预发布版本,现在您终于可以在请求处理程序中拒绝一个promise并且它将被正确处理:
中间件和处理程序现在可以返回promises,如果promise被拒绝,next(err)将被调用,其中err是拒绝的值。(来源) 例如:
app.get('/', async () => {
    throw new Error();
});
app.use((err, req, res, next) => {
    res.status(500).send('unexpected error :(');
});

然而,仅将其作为后备方案使用。正确的错误处理应该仍然发生在请求处理程序内部的catch短语中,使用适当的错误状态码。


1
如果您的应用程序未捕获未捕获的异常,它将因崩溃而退出,这意味着服务器进程将以非零错误代码退出,并且用户将永远看不到任何响应。 如果您没有像pm2这样的进程管理器安装,则它也将保持死亡状态。 为避免此情况,并捕获每种可能的错误,例如逻辑错误或程序员错误,您需要将代码放置在一个try-catch块中。 但是,有一个非常简单的解决方案,可以避免在每个单独的控制器函数周围放置try-catch块。这篇article文章解释了如何做到这一点。
只需安装express-async-errors并将其放置在您的app.js顶部即可。
const express = require('express');
require('express-async-errors');
...

现在控制器中的每个可能出现的错误都会自动传递给express error handler,无论是异步还是同步,即使您编写类似于null.test的内容。您可以像下面这样定义错误处理程序:

app.use((err, req, res, next) => {
    console.error(err.stack)
    res.status(500).send('Something broke!')
});

为了使其更有用,您甚至可以定义自己的错误类,继承自Error,并在控制器中任何位置throw它,Express将自动捕获它。然后在您的错误处理程序中检查if (err instanceof MyCustomErrorClass)以显示自定义消息在您的500页中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接