使用Mongoose处理错误

Son*_*123 27 rest error-handling mongoose

我是一个绝对的NodeJS初学者,想要用Express和Mongoose创建一个简单的REST-Web服务.

什么是在一个中心位置处理猫鼬错误的最佳做法?

当发生数据库错误的任何地方时,我想返回一个带有错误消息的Http-500-Error-Page:

if(error) {
  res.writeHead(500, {'Content-Type': 'application/json'});
  res.write('{error: "' + error + '"}');
  res.end();
}
Run Code Online (Sandbox Code Playgroud)

在旧教程http://blog-next-stage.learnboost.com/mongoose/中我读到了一个全局错误监听器:

Mongoose.addListener('error',function(errObj,scope_of_error));
Run Code Online (Sandbox Code Playgroud)

但这似乎不起作用,我在Mongoose官方文档中找不到关于这个监听器的东西.我是否在每次Mongo请求后检查错误?

aar*_*ann 43

如果您使用的是Express,通常会直接在路由中或在mongoose之上构建的api中处理错误,并将错误转发给next.

app.get('/tickets', function (req, res, next) {
  PlaneTickets.find({}, function (err, tickets) {
    if (err) return next(err);
    // or if no tickets are found maybe
    if (0 === tickets.length) return next(new NotFoundError));
    ...
  })
})
Run Code Online (Sandbox Code Playgroud)

NotFoundError会在你嗤之以鼻的错误处理中间件提供量身定制的消息.

有些抽象是可能的,但您仍然需要访问该next方法才能将错误传递到路径链中.

PlaneTickets.search(term, next, function (tickets) {
  // i don't like this b/c it hides whats going on and changes the (err, result) callback convention of node
})
Run Code Online (Sandbox Code Playgroud)

至于集中处理猫鼬错误,并不是真正处理所有错误的地方.错误可以在几个不同的级别处理:

connectionconnection你的模型正在使用的错误发出,所以

mongoose.connect(..);
mongoose.connection.on('error', handler);

// or if using separate connections
var conn = mongoose.createConnection(..);
conn.on('error', handler);
Run Code Online (Sandbox Code Playgroud)

对于典型的查询/更新/删除,错误将传递给您的回调.

PlaneTickets.find({..}, function (err, tickets) {
  if (err) ...
Run Code Online (Sandbox Code Playgroud)

如果您没有传递回调,那么如果您正在侦听它,则会在模型上发出错误:

PlaneTickets.on('error', handler); // note the loss of access to the `next` method from the request!
ticket.save(); // no callback passed
Run Code Online (Sandbox Code Playgroud)

如果您没有传递回调并且没有在model级别上侦听错误,那么它们将在模型上发出connection.

这里的关键要点是,您希望以next某种方式访问以传递错误.


538*_*MEO 2

IMO 更简单、更最新的解决方案:

连接错误

实例化数据库时处理连接级别错误:

const mongooseConnection = mongoose.createConnection(databaseURL)

mongooseConnection.on('error', err => {
  throw new Error('Mongo database connexion error')
})
Run Code Online (Sandbox Code Playgroud)

查询级别错误

如果您编写错误类型的字段或不正确地使用查询选项(填充、排序...),则可能会出现查询级别错误。try catch await我们可以使用比旧语法更具可读性/最新的语法promise.callback.catchmongoose doc

try {
  await Band.findOne({ _id: badId }).exec();
} catch (err) {
  throw new Error('Mongo database connexion error')
}
Run Code Online (Sandbox Code Playgroud)

奖励:建议和良好实践

=> 在您的所有应用程序中使用错误处理程序

上面 2 个示例中的错误处理不是很相关,因为您丢失了所有上下文信息(mongo 错误消息、堆栈跟踪、数据库名称、userId...)

您可以以集中的方式处理错误,以便可以将上下文信息与错误消息一起传递,如下所示:

try { ... } catch (err) {
  throw ApplicationError(
    'myMessage',
    {
      // Here we pass additional informations
      // this will help handling the error on the express api side
      httpCode: err instanceof mongoose.Error.ValidationError ? 422 : 500,
      // with this one you can display original error messsage/stack trace
      originalError: err, 
      // other contextual informations may be useful to display
      // but be carreful not to display sensitive informations
      // here (Eg DB connection string)
      databaseName,
      userId,
      userRole,
      methodName,
    }
  )
}

class ApplicationError extends Error {
  constructor(errMsg, additionalInfos) {
    // Here handle error as you want
  }
}
Run Code Online (Sandbox Code Playgroud)

猫鼬抛出的所有错误类型的列表

=> 使用请求处理程序以相同的方式处理所有猫鼬查询

避免重复代码的常见模式是创建一个函数来为您处理 mongoose Promise 的执行:

async function afterRequest(mongoosePromise, { sort, page, limit }) {
  try {
    if (sort) promise.sort(sort)

    // PAGINATION
    if (page) promise.skip(localConfig.page * limit || 25).limit(limit || 25)
    else if (limit) promise.limit(limit)

    return await promise.exec()

  } catch (err) { /** handle error like in the above code */ }
}
Run Code Online (Sandbox Code Playgroud)

所以我们现在可以这样使用它:

const mongoosePromise = Band.find()
afterRequest(mongoosePromise, { page: 1, limit: 10 })

Run Code Online (Sandbox Code Playgroud)

这种模式的优点是:

  • 在同一位置处理所有内容,因此这是添加用于安全处理或验证的自定义代码的最安全位置
  • 轻松地将一些选项(如分页)公开给 api 或一些您可能不希望直接处理 mongoose 请求的服务

上面只是一个非常简单的示例,但更进一步,我们可以实现以下一些功能:

  • 检查用户是否有填充权限
  • 屏蔽过滤器中的某些字段或与用户权限相关的更新/创建字段(例如,简单用户可能无法写入他的权限)
  • 强制执行某些过滤器(companyAdmin 可能只能访问其公司,因此我们希望强制执行过滤器{ companyId: user.companyId }
  • ...