如何修复“本地捕获的异常”?

Posted

技术标签:

【中文标题】如何修复“本地捕获的异常”?【英文标题】:How to fix "'throw' of exception caught locally"? 【发布时间】:2018-04-11 11:00:42 【问题描述】:

在此处理 REST API 调用的函数中,任何用于处理部分请求的被调用函数都可能引发错误,以表明应发送错误代码作为响应。但是,函数本身也可能发现错误,此时它应该跳转到异常处理块。

static async handleRequest(req) 
    try 
        let isAllowed = await checkIfIsAllowed(req);
        if (!isAllowed) 
            throw new ForbiddenException("You're not allowed to do that.");
        
        let result = await doSomething(req); // can also raise exceptions
        sendResult(result);
     catch(err) 
        sendErrorCode(err);
    

Webstorm 将在 throw 下划线并显示以下消息:'throw' of exception caught locally. This inspection reports any instances of javascript throw statements whose exceptions are always caught by containing try statements. Using throw statements as a "goto" to change the local flow of control is likely to be confusing.

但是,我不确定如何重构代码来改善这种情况。

我可以将catch 块中的代码复制粘贴到if 检查中,但我相信这会降低我的代码可读性和维护难度。

我可以编写一个新函数来执行isAllowed 检查并在不成功时抛出异常,但这似乎是在回避问题,而不是修复 Webstorm 应该报告的设计问题。

我们是否以错误的方式使用异常,这就是我们遇到此问题的原因,还是 Webstorm 错误只是误导并应该被禁用?

【问题讨论】:

@matchish 嘿 - 刚刚注意到这个赏金。我不确定您认为我的回答的哪一部分违反了 DRY 原则?我唯一能想到的是sendErrorCode - 但它没有被逐字重复;在一个地方,它从这个代码块发送一个非常具体的错误,在更一般的catch 它发送一个更一般的错误,没有被编码......? 【参考方案1】:

返回一个 promise 拒绝而不是在 try 块中抛出错误

  try 
    const isAllowed = await checkIfIsAllowed(request);

    if (!isAllowed) 
      return Promise.reject(Error("You're not allowed to do that."));
    

    const result = await doSomething(request);

    sendResult(result);
   catch (error) 
    throw error;
  

【讨论】:

请说明您的解决方案优于其他解决方案的原因。 @iron9 这只是这里提到的解决方案的更清洁的替代品。【参考方案2】:

与 James Thorpe 的观点相反,我更喜欢投掷的模式。我没有看到任何令人信服的理由来处理 try 块中的本地错误与从调用堆栈深处冒出的错误有什么不同......只需抛出它们。在我看来,这是对一致性的更好应用。

因为这种模式更加一致,当您想将 try 块中的逻辑提取到可能在另一个模块/文件中的另一个函数时,它自然更适合重构。

// main.js
try 
  if (!data) throw Error('missing data')
 catch (error) 
  handleError(error)


// Refactor...

// validate.js
function checkData(data) 
  if (!data) throw Error('missing data')


// main.js
try 
  checkData(data)
 catch (error) 
  handleError(error)

如果你处理错误而不是抛出 try 块,那么如果你在 try 块之外重构它,逻辑就必须改变。

另外,处理错误还有一个缺点,就是让你记得提前返回,这样try块就不会在遇到错误后继续执行逻辑。这很容易忘记。

try 
  if (!data) 
    handleError(error)
    return // if you forget this, you might execute code you didn't mean to. this isn't a problem with throw.
  
  // more logic down here
 catch (error) 
  handleError(error)

如果您担心哪种方法的性能更高,则不必担心。从技术上讲,处理错误的性能更高,但两者之间的差异绝对是微不足道的。

考虑一下 WebStorm 在这里有点过于固执的可能性。 ESLint 甚至没有为此制定规则。两种模式都是完全有效的。

【讨论】:

我同意,但我相信这取决于具体情况。但是对于所有可能的错误都以相同方式处理的情况,我认为它使代码更具可读性。 这应该是答案 同意。这只是那些没有任何区别的荒谬标准之一。 此外,如果您有自己的自定义错误类,有时可以很好地抛出不同类型的错误,然后将它们全部捕获在一个地方并根据它们的类型进行不同的处理。跨度> 【参考方案3】:

既然这不是阻塞错误,而只是一个IDE推荐,那么这个问题应该从两个方面来看。

第一方面是性能。如果这是一个瓶颈,并且有可能在编译或转移到 nodejs 的新(尚未发布)版本时使用它,那么重复的存在并不总是一个糟糕的解决方案。在这种情况下,IDE 似乎准确地暗示了这种情况,并且这种设计在某些情况下会导致优化不佳。

第二面是代码设计。如果它会使代码更具可读性并简化其他开发人员的工作 - 保留它。从这个角度来看,上面已经提出了解决方案。

【讨论】:

【参考方案4】:

对于“为什么不使用异常作为正常的流控制?”这个问题有很好的答案。 here.

不抛出您将在本地捕获的异常的原因是您在本地知道如何处理这种情况,因此根据定义,它不是异常的。

@James Thorpe 的 answer 在我看来不错,但 @matchish 觉得它违反了 DRY。我说一般来说,它没有。 DRY 代表 Don't Repeat Yourself,由创造短语的人定义为“每条知识都必须在系统中具有单一的、明确的、权威的表示”。应用于编写软件代码,就是不重复复杂代码。

实际上,任何被认为违反 DRY 的代码都可以通过将重复代码提取到一个函数中,然后从之前重复的位置调用该函数来称为“修复”。将代码的多个部分调用 sendErrorCode 是解决 DRY 问题的解决方案。所有关于如何处理错误的知识都集中在一个明确的位置,即sendErrorCode 函数。

我会稍微修改@James Thorpe 的回答,但这更像是一个狡辩而不是真正的批评,即sendErrorCode 应该接收异常对象或字符串,但不能同时接收两者:

static async handleRequest(req) 
    try 
        let isAllowed = await checkIfIsAllowed(req);
        if (!isAllowed) 
            sendErrorCode(new ForbiddenException("You're not allowed to do that."));
            return;
        
        let result = await doSomething(req); // can also raise exceptions
        sendResult(result);
     catch(err) 
        sendErrorCode(err);
    

更大的问题是错误的可能性有多大,将!isAllowed 视为例外是否合适。异常旨在处理异常或不可预测的情况。我希望!isAllowed 是正常事件,应该使用特定于该情况的逻辑来处理,不像突然无法查询具有isAllowed 问题答案的数据库。

@matchish 的proposed solution 将doSomethingOnAllowedRequest 的契约从永远不会抛出异常的事物更改为通常会抛出异常的事物,从而将异常处理的负担置于其所有调用者身上。通过导致多个调用者重复相同的错误处理代码,这很可能会导致违反 DRY,所以在摘要中我不喜欢它。在实践中,这将取决于整体情况,例如有多少调用者以及他们是否实际上对错误共享相同的响应。

【讨论】:

如果需要记录错误怎么办?你应该修改两行代码,这就是为什么我认为它不是 DRY。 你写的 !isAllowed 不是不可预知的情况,但我在你的代码中看到了 ForbiddenException。 handleRequest 是为了做一些有用的事情而创建的,当我们做不到的时候,我认为抛出异常是正常的。对我来说 sendErrorCode 看起来像是向前端抛出异常 @matchish "如果您需要记录错误怎么办?" 就这段代码而言,!isAllowed 不一定是错误/异常(它是已处理的用例),所以不需要记录它。但此时,我们正在讨论我们不知道这些事物的底层语义和用例的代码,这与本问答的初衷相去甚远。 我说的不是日志记录,它可以是其他任何东西。如果您不仅需要 sendErrorCode,则需要更改两段代码。 IMO 它不干燥 @matchish 如何在唯一错误处理代码 (sendErrorCode) 中记录错误,而不是在处理请求的代码中记录错误?【参考方案5】:

我认为 James Thorpe 的回答有一个缺点。它不是 DRY,在这两种情况下,当您调用 sendError 时,您都会处理异常。让我们想象一下,我们有很多行这样的逻辑代码,可以抛出异常。我认为它可以更好。

这是我的解决方案

async function doSomethingOnAllowedRequest(req) 
    let isAllowed = await checkIfIsAllowed(req);
    if (!isAllowed) 
       throw new ForbiddenException("You're not allowed to do that.");
    
    doSomething(req);

static async handleRequest(req) 
    try 
        let result = await doSomethingOnAllowedRequest(req);
        sendResult(result);
     catch(err) 
        sendErrorCode(err);
    

【讨论】:

它是 DRY,如果您不被允许执行该操作,您将发送一个非常具体的错误消息。所有其他例外情况都存在问题。 IMO,您只是将“不要对流逻辑控制使用异常”移动到此处的另一种方法中。与不抛出相比,抛出异常并不便宜(计算上)——如果你可以避免这样做,最好不要这样做。 我更喜欢可读性而不是性能。 IMO 这不仅仅是移动。 doSomething 也可以抛出异常,但我认为在这种情况下我们不能说我们使用异常进行流控制。如果不允许,您只能在允许的请求上做某事,您应该抛出异常。 使用try...catch...finally怎么样?我正在使用这样的语句来释放finally 块中的一些资源,并且try 块中的几个调用也可能引发异常。如果我不得不在本地中断try 流并释放这些资源,我无法提供比直接扔到那里更具可读性的解决方案。【参考方案6】:

如果isAllowed 失败,您正在检查某些内容并抛出异常,但您知道在这种情况下该怎么做 - 致电sendErrorCode。如果您不知道如何处理这种情况 - 即在特殊情况下,您应该向外部调用者抛出异常。

在这种情况下,如果发生这种情况,您已经定义了如何处理的流程 - 只需直接使用它,无需间接 throw/catch:

static async handleRequest(req) 
    try 
        let isAllowed = await checkIfIsAllowed(req);
        if (!isAllowed) 
            sendErrorCode("You're not allowed to do that.");
            return;
        
        let result = await doSomething(req); // can also raise exceptions
        sendResult(result);
     catch(err) 
        sendErrorCode(err);
    

我可以将 catch 块中的代码复制粘贴到 ifcheck 中,但我相信这会降低我的代码的可读性和维护难度。

相反,如上所述,我希望这是处理这种情况的方法。

【讨论】:

"如果你不知道如何处理这种情况,你应该向外部调用者抛出异常——即在特殊情况下。"如果这是真的,那就解释了这个问题。然而,这意味着我们在整个项目中使用了错误的异常,而不仅仅是这个函数。由于没有其他答案,我会接受这个。 James,我同意这个答案,但一个朋友给我发了这个:dev.to/nedsoft/central-error-handling-in-express-3aej你觉得怎么样? 如果你所要做的就是“sendErrorCode()”然后返回,这很好。但是,如果您必须执行多个操作来响应处理或清除错误,那么您最终会重复代码。如果在 try 块中有多个可以检测到内部错误的位置,情况会变得更糟。如果你必须在 catch 块之后做额外的工作,不管是否有错误,那么如果你在 try 块中返回,你就无法到达那里。 @wojtow 你的场景听起来好像可以通过重构来处理......【参考方案7】:

这可能会给你一些提示,也许这可能是原因(不确定是否相关)。 Catch statement does not catch thrown error

" 您的 try catch 块失败的原因是 ajax 请求是异步的。 try catch 块将在 Ajax 调用之前执行并自行发送请求,但在返回结果时会抛出错误,AT A LATER POINT IN TIME。

当 try catch 块执行时,没有错误。抛出错误时,没有try catch。如果您需要对 ajax 请求进行 try catch,请始终将 ajax try catch 块放在成功回调中,永远不要放在它之外。”

【讨论】:

以上是关于如何修复“本地捕获的异常”?的主要内容,如果未能解决你的问题,请参考以下文章

如何修复drv?

如何修复 npm 审计修复问题?

如何修复WMI

如何修复AppScan漏洞

如何在DOS环境下修复系统

如何修复这些漏洞? (npm audit fix 无法修复这些漏洞)