【问题标题】:Node.js When updating a document failed, is best to throw an exception or returning information?Node.js 更新文档失败时,最好是抛出异常还是返回信息?
【发布时间】:2021-01-06 05:57:22
【问题描述】:

我在一个类中有一个 updateDocument 方法,用于 node.js、express、mongoose 应用程序中的服务层。我想知道处理更新方法没有更新文档的情况的最佳做法是什么,例如如果传入了错误的 id。

版本 1:如果更新不成功,则返回一个带有 success: false 的对象:

  async updateDocument(id, updates) {
    const output = await this.DocumentModel.updateOne({ _id: id }, updates);

    let message = 'Something went wrong';
    let success = false;
    let updatedItem = null;
    if (output.nModified) {
      message = 'Successfully updated document.';
      success = true;
      updatedItem = await this.getDocument(id);
    }

    return { message, success, updatedItem};
  }

版本 2:如果更新不成功,则抛出错误:

  async updateDocument(id, updates) {
    const output = await this.DocumentModel.updateOne({ _id: id }, updates);

    let updatedItem;

    if (output.nModified) {
      updatedItem = await this.getDocument(id);
    } else{
      throw new Error("The document wasn't updated")
    }

    return updatedItem;
  }

当输入不正确时,您是否总是抛出异常,例如错误的 id?或者您能否返回有关更新是否成功的信息?作为新手 node.js 开发人员,我不确定我是否掌握了足够的全貌来识别这两种方法的问题。

【问题讨论】:

    标签: node.js mongodb express mongoose error-handling


    【解决方案1】:

    没有黄金之道,只有通向稳健且易于维护的软件的原则。

    一般来说,您应该使用 try-catch-statement 来处理您无法控制的所有类型的错误(连接、磁盘空间、凭据等)。然后应尽快处理错误,但不能提前处理。原因是你通常还不知道如何在较低层以适当的方式处理错误。

    对于您可以预期的逻辑“错误”(错误的输入格式、缺少用户名、未知选项等),您应该使用 if 语句或验证函数,然后抛出错误,如果有任何不符合预计。

    在您的情况下,您应该检查 updateOnegetDocument 方法是否会引发错误。如果是,您应该使用 try-catch-statement 包装这些函数。

    更多提示:

    • 您的代码的两个版本都很好。但我更喜欢版本 2,因为它更简洁。
    • 如果您确定始终存在output 对象,您可以像这样破坏nModified 属性:
    const { nModified } = await this.DocumentModel.updateOne({ _id: id }, updates);
    
    • 如果使用否定 if 语句,可以减少缩进深度并且可以使用 const 变量:
    if (!nModified) {
        throw new Error("The document wasn't updated")
    }
    
    const updatedItem = await this.getDocument(id);
    
    • 现在,您也可以直接返回this.getDocument(id),不再需要变量updatedItem
    • 您终于可以在控制器类中处理错误了。
    • 您可以使用自定义错误类在您的应用中保持一致的错误处理和错误消息。

    我希望这至少有点帮助。

    参考文献

    这些是一些类似的问题,答案很好。但是您需要小心,因为许多代码示例都不是现代 JavaScript。

    【讨论】:

    • “你终于可以在你的控制器类中处理你的错误了。”这对我来说最有意义,但我一直在读到你应该集中处理错误,即使在你附加的链接中也是如此。通过集中,我将其理解为在服务器文件中。我的理解正确吗?您如何看待集中处理它们而不是在控制器中处理它们?
    【解决方案2】:

    似乎对此有很多不同的意见,而不是一种首选方法。这是我找到的一些信息以及我最终做了什么。

    When to throw an exception?

    每个函数都会提出一个问题。如果给出的输入使该问题成为谬误,则抛出异常。使用返回 void 的函数很难画出这条线,但底线是:如果违反了函数对其输入的假设,它应该抛出异常而不是正常返回。

    Should a retrieval method return 'null' or throw an exception when it can't produce the return value?

    答案 1:

    无论你做什么,一定要记录下来。我认为这一点比究竟哪种方法“最好”更重要。

    答案 2:

    如果你总是希望找到一个值,那么如果它丢失了就抛出异常。异常意味着存在问题。

    如果值可能缺失或存在,并且两者都对应用程序逻辑有效,则返回 null。

    更重要的是:你在代码的其他地方做了什么?一致性很重要。

    应该在哪里处理异常?

    答案 1:在代码层中实际上可以对错误做点什么

    异常应该在实际上可以对错误做一些事情的代码层中处理。

    “记录并重新抛出”模式通常被认为是一种反模式(正是由于您提到的原因,这会导致大量重复代码,并且不能真正帮助您做任何实际的事情。)

    异常的意义在于它“不是预期的”。如果您正在工作的代码层在错误发生时无法做一些合理的事情来继续成功处理,那就让它冒泡吧。

    如果您正在处理的代码层可以在错误发生时继续执行某些操作,那么这就是处理错误的地方。 (并且返回“失败”的 http 响应代码算作“继续处理”的一种方式。您正在避免程序崩溃。)

    -source: softwareengineering.stackexchange

    答案 2:集中处理错误,而不是在中间件内

    如果没有一个专门的错误处理对象,由于处理不当,重要错误隐藏在雷达下的可能性就更大。错误处理程序对象负责使错误可见,例如通过写入格式良好的记录器,将事件发送到某些监控产品,如 Sentry、Rollbar 或 Raygun。大多数 Web 框架,如 Express,都提供了错误处理中间件机制。典型的错误处理流程可能是:某些模块抛出错误 -> API 路由器捕获错误 -> 将错误传播到负责捕获错误的中间件(例如 Express、KOA) -> 调用集中式错误处理程序 - > 中间件被告知此错误是否是不受信任的错误(不可操作),因此它可以正常重启应用程序。请注意,在 Express 中间件中处理错误是一种常见但错误的做法——这样做不会涵盖在非 Web 界面中引发的错误。

    -source; Handle errors centrally, not within a middleware

    所以这两个原则似乎不一致。 #1 说如果可以的话,马上处理。所以对我来说,它将在服务层中。但是#2说集中处理它,就像在服务器文件中一样。我选择了#2。

    我的决定:在自定义错误类中抛出错误

    它结合了人们建议的几种方法。我正在抛出错误,但我不是“记录并重新抛出”,正如上面的答案所警告的那样。相反,我将错误放入包含更多信息的自定义错误中并抛出。它被集中记录和处理。

    首先在我的服务层中,这是抛出错误的方式:

    async addUser(user) {
       let newUser;
       try {
          newUser = await this.UserModel.create(user);
       } catch (err) {
          throw new ApplicationError( // custom error
             {
                user, // params that are useful
                err, //original error
             },
            `Unable to create user: ${err.name}: ${err.message}` // error message
          );
       }
       return newUser;
    }
    

    ApplicationError 是一个自定义错误类,它接受一个信息对象和一条消息。我从这里得到了这个想法:

    在这种模式下,我们将使用ApplicationError 类启动我们的应用程序,这样我们就知道我们明确抛出的应用程序中的所有错误都将从它继承。所以我们将从以下错误类开始:

    -source: smashingmagazine

    您可以在您的自定义错误类中添加其他有用的信息,甚至可以使用什么 EJS 模板!因此,您可以真正创造性地处理错误,具体取决于您如何构建自定义错误类。我不知道这是否“正常”,也许包含 EJS 模板并不可靠,但我认为这是一个值得探索的有趣概念。您可以考虑其他更可靠的方式来动态响应错误。

    目前这是handleError 文件,但我可能会将其更改为处理自定义错误以创建一个信息更丰富的页面。 :

    const logger = require("./logger");
    
    module.exports = (err, req, res, next) => {
      if (res.headersSent) {
        return next(err);
      }
    
      logger.log("Error:", err);
      return res.status(500).render("500", {
        title: "500",
      });
    };
    

    然后我将该函数作为最后一个中间件添加到我的服务器文件中:

    app.use(handleError);
    

    总之,似乎在如何处理错误方面存在一些分歧,尽管似乎更多人认为您应该抛出错误并可能集中处理它。找到适合您的方法,保持一致并记录下来。

    【讨论】:

      猜你喜欢
      • 2011-11-09
      • 1970-01-01
      • 2011-04-27
      • 2010-12-05
      • 2014-03-15
      • 2011-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多