【问题标题】:Custom Error not propagated properly on throw statement or next() call自定义错误未在 throw 语句或 next() 调用上正确传播
【发布时间】:2015-03-22 07:09:39
【问题描述】:

我正在开发一个 Node.js/CoffeeScript 应用程序,我在其中使用类层次结构来处理错误。当我在路由处理程序的根目录中使用 throw 语句时,一切正常:

class APIError extends Error
    constructor: ->

app.error (err, req, res, next) ->
    if err instance of APIError
       console.log 'APIError captured'

app.get '/', (req, res, next) ->
    throw new APIError

但是,对于 Mongoose,在回调函数中使用 throw 语句:

app.get '/', (req, res, next) ->
    UserModel.where('name', 'Joe').findOne (err, doc) ->
        throw new APIError

结果

/usr/local/lib/node_modules/mongoose/lib/utils.js:413
    throw err;
          ^
Error: 

当我改为拨打next() 时,如

app.get '/', (req, res, next) ->
    UserModel.where('name', 'Joe').findOne (err, doc) ->
        return next new APIError

甚至在处理程序的主体中使用它:

app.get '/', (req, res, next) ->
    next new APIError

我在控制台中打印出undefined

将最后一条语句更改为return next Error 可以按预期工作,即控制台中正在打印异常堆栈跟踪:

app.get '/', (req, res, next) ->
    return next Error 'This works as expected'

是 Node.js 问题还是我在 CoffeeScript 中定义类的方式?任何想法如何使这种错误层次结构起作用?

更新 1

我可以确认这是 CoffeeScript 类的实现方式。使用标准的JS原型链定义解决了问题,但感觉不对。

【问题讨论】:

  • 是的,这绝对是 - 前几天我正在为类似的事情苦苦挣扎,没有正确抛出带有新自定义错误的扩展错误,或者实际上不是正确的类型,具体取决于在构造函数上 - 它的工作方式不太正确。

标签: node.js coffeescript mongoose


【解决方案1】:

在其构造函数中设置类的name属性可以解决问题(第3行):

class APIError extends Error
    constructor: ->
        @name = 'APIError'

app.use (err, req, res, next) ->
    if err instance of APIError
        console.log 'Error captured'

app.get '/', (req, res, next) ->
    #This will work: captured by error handler
    throw new APIError

app.get '/in-mongoose-callback', (req, res, next) ->
    UserModel.where('name', 'Joe').findOne (err, doc) ->
        #This one works as expected: captured by error handler
        return next new APIError

这是由于 one of the changes 登陆 CoffeeScript 1.3.3(2012 年 5 月 15 日):

由于 JavaScript 严格模式的新语义,CoffeeScript 不再保证构造函数在所有运行时都有名称。请参阅 #2052 进行讨论。

请注意,在 Mongoose 查询回调中使用 throw 语句而不是 next()将不起作用

【讨论】:

    【解决方案2】:

    在使用 express 时,我扩展了 Error,将名称设置为原型属性,并执行堆栈跟踪。

    class RequestError extends Error
        name: 'RequestError'
        constructor:(@status, message)->
            super message
            Error.captureStackTrace @, @constructor
    

    我可以为错误的用户登录执行new RequestError 403, 'Invalid username/password.',或为错误的页面请求执行new RequestError 404, 'Page not found.'。当我发现错误时,我会这样做

    require 'colors'
    console.error error.stack.red  # logs where any error occured
    if error instanceof RequestError
        # proper response for user errors
        response.status(error.status).send(error.message)
    else response.status(500).send('Internal Server Error') # for 'other' errors the user only gets back a 500 'Internal Server Error'
    

    【讨论】:

      【解决方案3】:

      很难说,但在我们将它们移到服务器脚本的末尾之前,我们遇到了错误问题,即使在启动服务器之后也是如此。这是使用 Express 和 Node,但可以给你一个提示。在我们在服务器文件的开头附近假设没有问题但在我们将所有错误处理移至结束后开始工作之前。不确定路由器或框架操作顺序的中间件是否有问题,但似乎解决了我们的问题。


      /////////////////////////////
      // Start Server
      /////////////////////////////
      
      app.listen(siteConf.port);
      
      //////////////////////////
      // Error Handling
      //////////////////////////  
      function NotFoundError(req, message){
          this.name = "NotFoundError";
          this.status = 404;
          this.message = message || (req.method + " " + req.url + " could not be found");
          Error.captureStackTrace(this, NotFoundError);
      }
      NotFoundError.prototype.__proto__ = Error.prototype;
      
      function ProtectedResourceError(req, message){
          this.name = "ProtectedResourceError";
          this.status = 401;
          this.message = message || (req.url + " is protected");
      }
      ProtectedResourceError.prototype.__proto__ = Error.prototype;
      
      function ValidationError(req, message, errors){
          this.name = "ValidationError";
          this.status = 400;
          this.message = message || ("Error when validating input for " + req.url);
          this.errors = errors;
      }
      ValidationError.prototype.__proto__ = Error.prototype;
      
      function SearchError(req, message){
          this.name = "SearchError";
          this.status = 400;
          this.message = message || ("Error when searching");
      }
      SearchError.prototype.__proto__ = Error.prototype;
      
      // If no route matches, throw NotFoundError
      app.use(function(req, res, next) {
          console.log("No matching route for " + req.url);
          next(new NotFoundError(req));        
      });
      
      // General error handler
      app.use(function(err, req, res, next) {
          //console.log("Trapped error : " + err);
      
          // handle exceptions
          if (err instanceof ValidationError) {
              standardResponse(req, res, {error: err.message, fields: err.errors}, err.status, err.message);
          } else if (err instanceof ProtectedResourceError) {
              standardResponse(req, res, {error: err.message}, err.status, err.message);
          } else if (err instanceof NotFoundError) {
              standardResponse(req, res, {error: err.message}, err.status, err.message);
          } else if (err instanceof SearchError) {
              standardResponse(req, res, {error: err.message}, err.status, err.message);
          } else {
              standardResponse(req, res, {error: err.message}, 500, err.message);
          }
      });
      

      【讨论】:

      • 我可以确认使用 JS 原型链定义类层次结构使 throw new APIError 在 Mongoose 查询回调中工作,但在 next new APIError 调用中不起作用。
      • 而不是声明“新”,您是否尝试过“下一个 APIError”?我不确定 Coffee Script 与 V8 语法的区别。我们在括号内声明: if (err) return next(new Error('Could not retrieve xxx'));
      • CoffeeScript 确实使用 JS [[Proto]] 链定义类层次结构。然而,这段代码也调用了Error.captureStackTrace,你可能也应该这样做。此外,在构造函数中调用 super 可能会有所帮助。
      猜你喜欢
      • 2015-09-08
      • 2019-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-04
      • 1970-01-01
      • 2021-06-30
      • 2015-02-28
      相关资源
      最近更新 更多