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

How to fix "'throw' of exception caught locally"?

在这个处理 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 错误只是误导,应该被禁用?

您正在检查某些内容并在 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);
    }
}

I could copypaste the code from the catch block into the ifcheck, but I believe this would make my code less readable and harder to maintain.

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

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

” 您的 try catch 块失败的原因是 ajax 请求是异步的。 try catch 块将在 Ajax 调用之前执行并发送请求本身,但在稍后返回结果时会抛出错误。

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

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);
    }
}

问题 "Why not use exceptions as normal flow control?" here.

有很好的答案

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

@James Thorpe 的 我觉得不错,但@matchish 觉得它违反了 DRY。我说一般情况下,它不会。 DRY 代表 Don't Repeat Yourself,由创造该短语的人定义为 "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system"。应用于编写软件代码,就是不重复 复杂 代码。

实际上,任何据说违反 DRY 的代码都被认为是 "fixed",方法是将重复的代码提取到一个函数中,然后从之前重复的地方调用该函数。让代码的多个部分调用 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 的 doSomethingOnAllowedRequest 的契约从永远不会抛出异常的东西更改为经常抛出异常的东西,将异常处理的负担放在它的所有调用者身上。这可能会导致多个调用者重复执行相同的错误处理代码,从而导致违反 DRY,因此在摘要中我不喜欢它。实际上,这将取决于整体情况,例如有多少呼叫者以及他们是否实际上对错误有相同的反应。

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

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

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

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

因为这种模式更加一致,当您想将 try 块中的逻辑提取到可能位于另一个 module/file.

中的另一个函数时,它自然更适合重构。
// 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 块之外重构它,则必须更改逻辑。

另外,处理错误的缺点是让你早点记住return,这样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 甚至没有这方面的规则。两种模式都完全有效。

Return 承诺拒绝而不是在 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;
  }