处理 JavaScript 错误的权威指南

已发表: 2022-01-24

墨菲定律指出,任何可能出错的事情最终都会出错。 这在编程世界中应用得太好了。 如果您创建一个应用程序,您很可能会产生错误和其他问题。 JavaScript 中的错误就是这样一个常见问题!

软件产品的成功取决于其创建者在伤害用户之前解决这些问题的能力。 在所有编程语言中,JavaScript 因其平均错误处理设计而臭名昭著。

如果您正在构建一个 JavaScript 应用程序,那么您很有可能会在某一时刻弄乱数据类型。 如果不是这样,那么您最终可能会将undefined替换为null或三等号运算符 ( === ) 与双等号运算符 ( == )。

犯错是人之常情。 这就是为什么我们将向您展示您需要了解的有关处理 JavaScript 错误的所有信息。

本文将引导您了解 JavaScript 中的基本错误,并解释您可能遇到的各种错误。 然后,您将学习如何识别和修复这些错误。 还有一些技巧可以在生产环境中有效地处理错误。

事不宜迟,让我们开始吧!

什么是 JavaScript 错误?

编程错误是指程序无法正常运行的情况。 当程序不知道如何处理手头的工作时,可能会发生这种情况,例如在没有网络连接的情况下尝试打开不存在的文件或访问基于 Web 的 API 端点时。

这些情况促使程序向用户抛出错误,说明它不知道如何继续。 该程序收集尽可能多的有关错误的信息,然后报告它无法继续前进。

墨菲定律指出,任何可能出错的事情最终都会出错 这在 JavaScript 的世界中应用得太好了 准备好本指南点击推

聪明的程序员试图预测和覆盖这些场景,这样用户就不必独立地找出像“404”这样的技术错误信息。 相反,它们显示了一条更容易理解的信息:“找不到该页面。”

JavaScript 中的错误是在发生编程错误时显示的对象。 这些对象包含有关错误类型、导致错误的语句以及发生错误时的堆栈跟踪的大量信息。 JavaScript 还允许程序员创建自定义错误,以便在调试问题时提供额外信息。

错误的属性

现在 JavaScript 错误的定义已经很清楚了,是时候深入研究细节了。

JavaScript 中的错误带有某些标准和自定义属性,有助于理解错误的原因和影响。 默认情况下,JavaScript 中的错误包含三个属性:

  1. message : 携带错误信息的字符串值
  2. name :发生的错误类型(我们将在下一节深入探讨)
  3. stack :发生错误时执行的代码的堆栈跟踪。

此外,error 还可以携带columnNumber、lineNumber、fileName等属性,以更好地描述错误。 但是,这些属性不是标准的,可能会出现在 JavaScript 应用程序生成的每个错误对象中,也可能不会出现。

了解堆栈跟踪

堆栈跟踪是发生异常或警告等事件时程序所在的方法调用列表。 这是伴随异常的示例堆栈跟踪的样子:

错误“TypeError: Numeric argument is expected”显示在灰色背景上,并带有其他堆栈详细信息。
堆栈跟踪示例。

如您所见,它首先打印错误名称和消息,然后是被调用的方法列表。 每个方法调用都说明其源代码的位置以及调用它的行。 您可以使用这些数据浏览您的代码库并确定是哪段代码导致了错误。

此方法列表以堆叠方式排列。 它显示了您的异常首次引发的位置以及它如何通过堆叠的方法调用传播。 为异常实现捕获不会让它通过堆栈向上传播并使您的程序崩溃。 但是,您可能希望在某些情况下故意不捕获致命错误以使程序崩溃。

错误与异常

大多数人通常将错误和异常视为同一件事。 但是,必须注意它们之间的细微但根本的区别。

为了更好地理解这一点,让我们举一个简单的例子。 以下是如何在 JavaScript 中定义错误:

 const wrongTypeError = TypeError("Wrong type found, expected character")

这就是wrongTypeError对象变成异常的方式:

 throw wrongTypeError

然而,大多数人倾向于使用在抛出错误对象时定义错误对象的简写形式:

 throw TypeError("Wrong type found, expected character")

这是标准做法。 然而,这也是开发人员倾向于混淆异常和错误的原因之一。 因此,即使您使用速记来快速完成工作,了解基础知识也至关重要。

JavaScript 中的错误类型

JavaScript 中有一系列预定义的错误类型。 只要程序员没有明确处理应用程序中的错误,它们就会由 JavaScript 运行时自动选择和定义。

本节将引导您了解 JavaScript 中一些最常见的错误类型,并了解它们发生的时间和原因。

范围错误

当变量设置为超出其合法值范围时,会引发 RangeError。 它通常发生在将值作为参数传递给函数时,并且给定值不在函数参数的范围内。 使用文档记录不佳的第三方库时,有时修复起来会很棘手,因为您需要知道参数的可能值范围才能传递正确的值。

RangeError 发生的一些常见场景是:

  • 试图通过 Array 构造函数创建一个非法长度的数组。
  • 将错误值传递给数字方法,例如toExponential()toPrecision()toFixed()等。
  • 将非法值传递给像normalize()这样的字符串函数。

参考错误

当您的代码中的变量引用出现问题时,就会发生 ReferenceError。 您可能忘记在使用变量之前为其定义值,或者您可能试图在代码中使用不可访问的变量。 在任何情况下,通过堆栈跟踪提供了充足的信息来查找和修复有错误的变量引用。

ReferenceErrors 发生的一些常见原因是:

  • 在变量名中打错字。
  • 试图访问其范围之外的块范围变量。
  • 在加载之前从外部库(如来自 jQuery 的 $)中引用全局变量。

语法错误

这些错误是最容易修复的错误之一,因为它们表明代码语法有错误。 由于 JavaScript 是一种解释而不是编译的脚本语言,因此当应用程序执行包含错误的脚本时会抛出这些脚本语言。 在编译语言的情况下,在编译过程中会识别出此类错误。 因此,在修复之前不会创建应用程序二进制文件。

可能发生 SyntaxErrors 的一些常见原因是:

  • 缺少引号
  • 缺少右括号
  • 花括号或其他字符的不正确对齐

最好在 IDE 中使用 linting 工具在此类错误出现在浏览器之前为您识别这些错误。

类型错误

TypeError 是 JavaScript 应用程序中最常见的错误之一。 当某些值不是特定的预期类型时,会创建此错误。 发生时的一些常见情况是:

  • 调用不是方法的对象。
  • 试图访问空对象或未定义对象的属性
  • 将字符串视为数字,反之亦然

发生 TypeError 的可能性还有很多。 稍后我们将查看一些著名的实例并学习如何修复它们。

内部错误

当 JavaScript 运行时引擎中发生异常时使用 InternalError 类型。 它可能表示也可能不表示您的代码存在问题。

通常,InternalError 仅在两种情况下发生:

  • 当 JavaScript 运行时的补丁或更新带有引发异常的错误时(这种情况很少发生)
  • 当你的代码包含对 JavaScript 引擎来说太大的实体时(例如太多的 switch case、太大的数组初始化器、太多的递归)

解决此错误的最合适方法是通过错误消息确定原因,并在可能的情况下重构您的应用程序逻辑,以消除 JavaScript 引擎上的工作负载突然激增。

URI错误

当非法使用decodeURIComponent等全局 URI 处理函数时,会发生 URIError。 它通常表示传递给方法调用的参数不符合 URI 标准,因此没有被方法正确解析。

诊断这些错误通常很容易,因为您只需要检查参数是否存在畸形。

评估错误

eval()函数调用发生错误时,会发生 EvalError。 eval()函数用于执行存储在字符串中的 JavaScript 代码。 但是,由于安全问题,我们强烈建议不要使用eval()函数,并且当前的 ECMAScript 规范不再抛出EvalError类,所以这种错误类型的存在只是为了保持与旧版 JavaScript 代码的向后兼容性。

如果您使用的是旧版本的 JavaScript,则可能会遇到此错误。 无论如何,最好调查在eval()函数调用中执行的代码是否有任何异常。

创建自定义错误类型

虽然 JavaScript 提供了足够的错误类型列表来涵盖大多数场景,但如果列表不满足您的要求,您始终可以创建新的错误类型。 这种灵活性的基础在于 JavaScript 允许您使用throw命令逐字地抛出任何东西。

因此,从技术上讲,这些声明是完全合法的:

 throw 8 throw "An error occurred"

但是,抛出原始数据类型不会提供有关错误的详细信息,例如其类型、名称或随附的堆栈跟踪。 为了解决这个问题并标准化错误处理过程,我们提供了Error类。 也不鼓励在抛出异常时使用原始数据类型。

您可以扩展Error类来创建您的自定义错误类。 以下是如何执行此操作的基本示例:

 class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } }

您可以通过以下方式使用它:

 throw ValidationError("Property not found: name")

然后您可以使用instanceof关键字来识别它:

 try { validateForm() // code that throws a ValidationError } catch (e) { if (e instanceof ValidationError) // do something else // do something else }

JavaScript 中最常见的 10 个错误

现在您已经了解了常见的错误类型以及如何创建自定义错误类型,是时候看看您在编写 JavaScript 代码时会遇到的一些最常见的错误了。

1. 未捕获的 RangeError

在几种不同的情况下,Google Chrome 中会出现此错误。 首先,如果您调用递归函数并且它不会终止,则可能会发生这种情况。 您可以在 Chrome 开发者控制台中自行查看:

错误“Uncaught RangeError: Maximum call stack size exceeded”显示在红色背景上,旁边是一个红色十字图标,上面有一个递归函数的代码。
带有递归函数调用的 RangeError 示例。

因此,要解决此类错误,请确保正确定义递归函数的边界情况。 发生此错误的另一个原因是您传递的值超出了函数的参数范围。 这是一个例子:

错误“未捕获的 RangeError:toExponential() 参数必须介于 0 和 100 之间”显示在红色背景上,旁边是一个红色的十字图标,上面有一个 toExponential() 函数调用。
带有 toExponential() 调用的 RangeError 示例。

错误消息通常会指出您的代码有什么问题。 一旦你做出改变,它就会得到解决。

num = 4. num.toExponential(2)。输出:4.00e+0。
toExponential() 函数调用的输出。

2. Uncaught TypeError: Cannot set property

当您在未定义的引用上设置属性时会发生此错误。 您可以使用此代码重现该问题:

 var list list.count = 0

这是您将收到的输出:

错误“Uncaught TypeError: Cannot set properties of undefined”显示在红色背景上,旁边是一个红色十字图标,上面有一个 list.count = 0 赋值。
类型错误示例。

要修复此错误,请在访问其属性之前使用值初始化引用。 以下是修复后的外观:

在使用 {} 初始化列表后设置 list.count = 10,因此输出为 10。
如何修复类型错误。

3. Uncaught TypeError: Cannot read property

这是 JavaScript 中最常出现的错误之一。 当您尝试读取属性或调用未定义对象的函数时,会发生此错误。 您可以通过在 Chrome 开发人员控制台中运行以下代码来非常轻松地重现它:

 var func func.call()

这是输出:

错误“Uncaught TypeError: Cannot read properties of undefined”显示在红色背景上,红色十字图标旁边带有 func.call()。
带有未定义函数的 TypeError 示例。

未定义的对象是导致此错误的众多可能原因之一。 此问题的另一个突出原因可能是在呈现 UI 时未正确初始化状态。 这是来自 React 应用程序的真实示例:

 import React, { useState, useEffect } from "react"; const CardsList = () => { const [state, setState] = useState(); useEffect(() => { setTimeout(() => setState({ items: ["Card 1", "Card 2"] }), 2000); }, []); return ( <> {state.items.map((item) => ( <li key={item}>{item}</li> ))} </> ); }; export default CardsList;

该应用程序从一个空状态容器开始,并在延迟 2 秒后提供一些项目。 延迟用于模拟网络调用。 即使您的网络速度非常快,您仍然会面临轻微的延迟,因为该组件将至少呈现一次。 如果您尝试运行此应用程序,您将收到以下错误:

灰色背景显示错误“未定义不是对象”。
浏览器中的 TypeError 堆栈跟踪。

这是因为,在渲染时,状态容器是未定义的; 因此,上面不存在任何属性items 。 修复这个错误很容易。 您只需要为状态容器提供初始默认值。

 // ... const [state, setState] = useState({items: []}); // ...

现在,在设置延迟之后,您的应用程序将显示类似的输出:

一个项目符号列表,其中包含两个项目,分别为“卡 1”和“卡 2”。
代码输出。

代码中的确切修复可能会有所不同,但这里的本质是始终在使用变量之前正确初始化它们。

4. TypeError: 'undefined' is not an object

当您尝试访问未定义对象的属性或调用未定义对象的方法时,会在 Safari 中发生此错误。 您可以从上面运行相同的代码来自己重现错误。

错误“TypeError: undefined is not an object”显示在红色感叹号图标旁边的红色背景上,上面有 func.call() 方法调用。
带有未定义函数的 TypeError 示例。

这个错误的解决方法也是一样的——确保你已经正确地初始化了你的变量,并且在访问一个属性或方法时它们不是未定义的。

5. TypeError: null is not an object

这又与前面的错误相似。 它发生在 Safari 上,这两个错误之间的唯一区别是,当正在访问其属性或方法的对象为null而不是undefined时,会抛出这个错误。 您可以通过运行以下代码来重现这一点:

 var func = null func.call()

这是您将收到的输出:

“TypeError: null is not an object”错误消息,显示在红色感叹号图标旁边的红色背景上。
带有 null 函数的 TypeError 示例。

因为null是显式设置为变量的值,而不是由 JavaScript 自动分配的值。 仅当您尝试访问自己设置为null的变量时,才会发生此错误。 因此,您需要重新访问您的代码并检查您编写的逻辑是否正确。

6. TypeError: 无法读取属性'length'

当您尝试读取nullundefined对象的长度时,Chrome 会出现此错误。 这个问题的原因和前面的问题类似,但是在处理列表的时候出现的频率比较高; 因此值得特别提及。 以下是重现问题的方法:

错误“Uncaught TypeError: Cannot read property 'length' of undefined”显示在红色背景上的红色十字图标旁边,上面有 myButton.length 调用。
带有未定义对象的 TypeError 示例。

但是,在较新版本的 Chrome 中,此错误报告为Uncaught TypeError: Cannot read properties of undefined 。 这是它现在的样子:

红色背景上显示的错误“未捕获的类型错误:无法读取未定义的属性”,旁边是红色十字图标,上面有 myButton.length 调用。
在较新的 Chrome 版本上带有未定义对象的 TypeError 示例。

再次,修复是确保您尝试访问其长度的对象存在并且未设置为null

7. TypeError: 'undefined' is not a function

当您尝试调用脚本中不存在的方法或该方法存在但无法在调用上下文中引用时,会发生此错误。 这个错误通常发生在谷歌浏览器中,您可以通过检查抛出错误的代码行来解决它。 如果您发现拼写错误,请修复它并检查它是否能解决您的问题。

如果您在代码中使用了自引用关键字this ,如果this没有适当地绑定到您的上下文,则可能会出现此错误。 考虑以下代码:

 function showAlert() { alert("message here") } document.addEventListener("click", () => { this.showAlert(); })

如果你执行上面的代码,它会抛出我们讨论过的错误。 这是因为作为事件侦听器传递的匿名函数正在document的上下文中执行。

相反,函数showAlert是在window的上下文中定义的。

要解决这个问题,您必须通过使用bind()方法绑定函数来传递对函数的正确引用:

 document.addEventListener("click", this.showAlert.bind(this))

8. ReferenceError: event is not defined

当您尝试访问未在调用范围内定义的引用时,会发生此错误。 这通常在处理事件时发生,因为它们通常会在回调函数中为您提供一个名为event的引用。 如果您忘记在函数的参数中定义事件参数或拼写错误,则可能会发生此错误。

此错误可能不会在 Internet Explorer 或 Google Chrome 中发生(因为 IE 提供了一个全局事件变量并且 Chrome 会自动将事件变量附加到处理程序),但它可能会在 Firefox 中发生。 所以建议留意这样的小错误。

9. TypeError:赋值给常量变量

这是由于粗心造成的错误。 如果您尝试将新值分配给常量变量,您将遇到这样的结果:

错误“Uncaught TypeError: Assignment to constant variable”显示在红色背景上的红色十字图标旁边,上面有 func = 6 赋值。
带有常量对象分配的 TypeError 示例。

虽然现在看起来很容易修复,但想象一下数百个这样的变量声明,其中一个被错误地定义为const而不是let ! 与 PHP 等其他脚本语言不同,在 JavaScript 中声明常量和变量的风格差别很小。 因此,当您遇到此错误时,建议首先检查您的声明。 如果您忘记上述引用是一个常量并将其用作变量,您也可能会遇到此错误。 这表明您的应用程序逻辑存在粗心或缺陷。 尝试解决此问题时,请务必检查此项。

10.(未知):脚本错误

当第三方脚本向您的浏览器发送错误时,就会发生脚本错误。 此错误后跟(未知),因为第三方脚本与您的应用属于不同的域。 浏览器隐藏了其他细节,以防止第三方脚本泄露敏感信息。

在不了解完整详细信息的情况下,您无法解决此错误。 您可以执行以下操作来获取有关该错误的更多信息:

  1. 在脚本标签中添加crossorigin属性。
  2. 在托管脚本的服务器上设置正确的Access-Control-Allow-Origin标头。
  3. [可选] 如果您无权访问托管脚本的服务器,您可以考虑使用代理将您的请求中继到服务器并返回到带有正确标头的客户端。

一旦您可以访问错误的详细信息,您就可以着手解决问题,这可能与第三方库或网络有关。

如何识别和防止 JavaScript 中的错误

虽然上面讨论的错误是 JavaScript 中最常见和最常见的错误,但您会遇到,仅仅依靠几个示例是远远不够的。 在开发 JavaScript 应用程序时,了解如何检测和防止任何类型的错误至关重要。 以下是如何处理 JavaScript 中的错误。

手动抛出和捕获错误

处理手动或运行时抛出的错误的最基本方法是捕获它们。 与大多数其他语言一样,JavaScript 提供了一组关键字来处理错误。 在着手处理 JavaScript 应用程序中的错误之前,必须深入了解它们中的每一个。

该集合的第一个也是最基本的关键字是throw 。 很明显,throw 关键字用于抛出错误以在 JavaScript 运行时手动创建异常。 我们已经在本文前面讨论过这个问题,这里是这个关键字意义的要点:

  • 你可以throw任何东西,包括数字、字符串和Error对象。
  • 但是,不建议抛出诸如字符串和数字之类的原始数据类型,因为它们不携带有关错误的调试信息。
  • 示例: throw TypeError("Please provide a string")

尝试

try关键字用于指示代码块可能会引发异常。 它的语法是:

 try { // error-prone code here }

需要注意的是, catch块必须始终跟在try块之后才能有效地处理错误。

抓住

catch关键字用于创建一个 catch 块。 此代码块负责处理尾随try块捕获的错误。 这是它的语法:

 catch (exception) { // code to handle the exception here }

这就是一起实现trycatch块的方式:

 try { // business logic code } catch (exception) { // error handling code }

与 C++ 或 Java 不同,您不能将多个catch块附加到 JavaScript 中的try块。 这意味着您不能这样做:

 try { // business logic code } catch (exception) { if (exception instanceof TypeError) { // do something } } catch (exception) { if (exception instanceof RangeError) { // do something } }

相反,您可以在单个 catch 块中使用if...else语句或 switch case 语句来处理所有可能的错误情况。 它看起来像这样:

 try { // business logic code } catch (exception) { if (exception instanceof TypeError) { // do something } else if (exception instanceof RangeError) { // do something else } }

最后

finally关键字用于定义处理错误后运行的代码块。 该块在 try 和 catch 块之后执行。

此外,无论其他两个块的结果如何,都会执行 finally 块。 这意味着即使catch块不能完全处理错误或者catch块中抛出错误,解释器也会在程序崩溃之前执行finally块中的代码。

要被认为是有效的,JavaScript 中的 try 块需要后跟 catch 或 finally 块。 如果没有这些,解释器将引发 SyntaxError。 因此,在处理错误时,请确保至少遵循您的 try 块。

使用 onerror() 方法全局处理错误

onerror()方法可用于所有 HTML 元素,以处理它们可能发生的任何错误。 例如,如果img标签找不到指定 URL 的图像,它会触发其 onerror 方法以允许用户处理错误。

通常,您会在 onerror 调用中提供另一个图像 URL,以便img标记回退到。 这是您可以通过 JavaScript 执行此操作的方法:

 const image = document.querySelector("img") image.onerror = (event) => { console.log("Error occurred: " + event) }

但是,您可以使用此功能为您的应用创建全局错误处理机制。 以下是您的操作方法:

 window.onerror = (event) => { console.log("Error occurred: " + event) }

使用此事件处理程序,您可以摆脱代码中的多个try...catch块,并集中您的应用程序的错误处理,类似于事件处理。 您可以将多个错误处理程序附加到窗口,以维护 SOLID 设计原则中的单一责任原则。 解释器将循环遍历所有处理程序,直到到达适当的处理程序。

通过回调传递错误

虽然简单和线性函数允许错误处理保持简单,但回调会使事情复杂化。

考虑以下代码:

需要一个可以为您提供竞争优势的托管解决方案? Kinsta 为您提供令人难以置信的速度、最先进的安全性和自动缩放功能。 查看我们的计划

const calculateCube = (number, callback) => { setTimeout(() => { const cube = number * number * number callback(cube) }, 1000) } const callback = result => console.log(result) calculateCube(4, callback)

上面的函数演示了一个异步条件,其中一个函数需要一些时间来处理操作并稍后在回调的帮助下返回结果。

如果您尝试在函数调用中输入一个字符串而不是 4,您将得到NaN作为结果。

这需要妥善处理。 就是这样:

 const calculateCube = (number, callback) => { setTimeout(() => { if (typeof number !== "number") throw new Error("Numeric argument is expected") const cube = number * number * number callback(cube) }, 1000) } const callback = result => console.log(result) try { calculateCube(4, callback) } catch (e) { console.log(e) }

这应该可以理想地解决问题。 但是,如果您尝试将字符串传递给函数调用,您将收到以下信息:

红色十字图标旁边的深红色背景上显示错误“未捕获的错误:预期为数字参数”。
错误参数的错误示例。

即使您在调用函数时实现了 try-catch 块,它仍然表示错误未捕获。 由于超时延迟,在执行 catch 块后抛出错误。

这可能在网络调用中很快发生,在这种情况下会出现意外延迟。您需要在开发应用程序时涵盖此类情况。

以下是在回调中正确处理错误的方法:

 const calculateCube = (number, callback) => { setTimeout(() => { if (typeof number !== "number") { callback(new TypeError("Numeric argument is expected")) return } const cube = number * number * number callback(null, cube) }, 2000) } const callback = (error, result) => { if (error !== null) { console.log(error) return } console.log(result) } try { calculateCube('hey', callback) } catch (e) { console.log(e) }

现在,控制台的输出将是:

深灰色背景上显示消息“TypeError: Numeric argument is expected”。
带有非法参数的 TypeError 示例。

这表明错误已得到适当处理。

处理 Promise 中的错误

大多数人倾向于使用 Promise 来处理异步活动。 Promise 还有另一个优点——被拒绝的 Promise 不会终止你的脚本。 但是,您仍然需要实现一个 catch 块来处理 Promise 中的错误。 为了更好地理解这一点,让我们使用 Promises 重写calculateCube()函数:

 const delay = ms => new Promise(res => setTimeout(res, ms)); const calculateCube = async (number) => { if (typeof number !== "number") throw Error("Numeric argument is expected") await delay(5000) const cube = number * number * number return cube } try { calculateCube(4).then(r => console.log(r)) } catch (e) { console.log(e) }

前面代码中的超时已经被隔离到delay函数中以便理解。 如果您尝试输入一个字符串而不是 4,您获得的输出将类似于以下内容:

深灰色背景上显示的错误“未捕获(承诺中)错误:预期为数字参数”,旁边有一个红十字图标。
在 Promise 中带有非法参数的 TypeError 示例。

同样,这是由于Promise在其他所有内容完成执行后抛出错误。 这个问题的解决方案很简单。 只需像这样向 Promise 链添加一个catch()调用:

 calculateCube("hey") .then(r => console.log(r)) .catch(e => console.log(e))

现在输出将是:

深灰色背景上显示消息“错误:需要数字参数”。
处理带有非法参数的 TypeError 示例。

您可以观察到使用 Promise 处理错误是多么容易。 此外,您可以链接finally()块和 promise 调用以添加将在错误处理完成后运行的代码。

或者,您也可以使用传统的 try-catch-finally 技术来处理 Promise 中的错误。 在这种情况下,您的 promise 调用如下所示:

 try { let result = await calculateCube("hey") console.log(result) } catch (e) { console.log(e) } finally { console.log('Finally executed") }

但是,这仅适用于异步函数。 因此,处理 Promise 中的错误的最优选方式是链式catchfinally连接到 Promise 调用。

throw/catch vs onerror() vs Callbacks vs Promises:哪个是最好的?

有四种方法可供您使用,您必须知道如何在任何给定的用例中选择最合适的方法。 以下是您可以自己决定的方法:

扔/接

您将在大多数情况下使用此方法。 确保在你的 catch 块中为所有可能的错误实现条件,如果你需要在 try 块之后运行一些内存清理例程,请记住包含一个 finally 块。

但是,太多的 try/catch 块会使您的代码难以维护。 如果您发现自己处于这种情况,您可能希望通过全局处理程序或 promise 方法来处理错误。

在异步 try/catch 块和 promise 的catch()之间做出决定时,建议使用异步 try/catch 块,因为它们会使您的代码线性且易于调试。

错误()

当您知道您的应用程序必须处理许多错误并且它们可以很好地分散在整个代码库中时,最好使用onerror()方法。 onerror方法使您能够处理错误,就好像它们只是您的应用程序处理的另一个事件一样。 您可以定义多个错误处理程序并在初始呈现时将它们附加到应用程序的窗口。

但是,您还必须记住,在错误范围较小的较小项目中设置onerror()方法可能会带来不必要的挑战。 如果您确定您的应用程序不会抛出太多错误,那么传统的 throw/catch 方法将最适合您。

回调和承诺

回调和承诺中的错误处理因代码设计和结构而异。 但是,如果您在编写代码之前在这两者之间进行选择,最好使用 Promise。

这是因为 Promise 有一个内置结构,用于链接catch()finally()块来轻松处理错误。 这种方法比定义附加参数/重用现有参数来处理错误更容易和更清晰。

使用 Git 存储库跟踪更改

由于代码库中的手动错误,经常会出现许多错误。 While developing or debugging your code, you might end up making unnecessary changes that may cause new errors to appear in your codebase. Automated testing is a great way to keep your code in check after every change. However, it can only tell you if something's wrong. If you don't take frequent backups of your code, you'll end up wasting time trying to fix a function or a script that was working just fine before.

This is where git plays its role. With a proper commit strategy, you can use your git history as a backup system to view your code as it evolved through the development. You can easily browse through your older commits and find out the version of the function working fine before but throwing errors after an unrelated change.

You can then restore the old code or compare the two versions to determine what went wrong. Modern web development tools like GitHub Desktop or GitKraken help you to visualize these changes side by side and figure out the mistakes quickly.

A habit that can help you make fewer errors is running code reviews whenever you make a significant change to your code. If you're working in a team, you can create a pull request and have a team member review it thoroughly. This will help you use a second pair of eyes to spot out any errors that might have slipped by you.

Best Practices for Handling Errors in JavaScript

The above-mentioned methods are adequate to help you design a robust error handling approach for your next JavaScript application. However, it would be best to keep a few things in mind while implementing them to get the best out of your error-proofing. Here are some tips to help you.

1. Use Custom Errors When Handling Operational Exceptions

We introduced custom errors early in this guide to give you an idea of how to customize the error handling to your application's unique case. It's advisable to use custom errors wherever possible instead of the generic Error class as it provides more contextual information to the calling environment about the error.

On top of that, custom errors allow you to moderate how an error is displayed to the calling environment. This means that you can choose to hide specific details or display additional information about the error as and when you wish.

You can go so far as to format the error contents according to your needs. This gives you better control over how the error is interpreted and handled.

2. Do Not Swallow Any Exceptions

Even the most senior developers often make a rookie mistake — consuming exceptions levels deep down in their code.

You might come across situations where you have a piece of code that is optional to run. If it works, great; if it doesn't, you don't need to do anything about it.

In these cases, it's often tempting to put this code in a try block and attach an empty catch block to it. However, by doing this, you'll leave that piece of code open to causing any kind of error and getting away with it. This can become dangerous if you have a large codebase and many instances of such poor error management constructs.

The best way to handle exceptions is to determine a level on which all of them will be dealt and raise them until there. This level can be a controller (in an MVC architecture app) or a middleware (in a traditional server-oriented app).

This way, you'll get to know where you can find all the errors occurring in your app and choose how to resolve them, even if it means not doing anything about them.

3. Use a Centralized Strategy for Logs and Error Alerts

Logging an error is often an integral part of handling it. Those who fail to develop a centralized strategy for logging errors may miss out on valuable information about their app's usage.

An app's event logs can help you figure out crucial data about errors and help to debug them quickly. If you have proper alerting mechanisms set up in your app, you can know when an error occurs in your app before it reaches a large section of your user base.

It's advisable to use a pre-built logger or create one to suit your needs. You can configure this logger to handle errors based on their levels (warning, debug, info, etc.), and some loggers even go so far as to send logs to remote logging servers immediately. This way, you can watch how your application's logic performs with active users.

4. Notify Users About Errors Appropriately

Another good point to keep in mind while defining your error handling strategy is to keep the user in mind.

All errors that interfere with the normal functioning of your app must present a visible alert to the user to notify them that something went wrong so the user can try to work out a solution. If you know a quick fix for the error, such as retrying an operation or logging out and logging back in, make sure to mention it in the alert to help fix the user experience in real-time.

In the case of errors that don't cause any interference with the everyday user experience, you can consider suppressing the alert and logging the error to a remote server for resolving later.

5. Implement a Middleware (Node.js)

The Node.js environment supports middlewares to add functionalities to server applications. You can use this feature to create an error-handling middleware for your server.

The most significant benefit of using middleware is that all of your errors are handled centrally in one place. You can choose to enable/disable this setup for testing purposes easily.

Here's how you can create a basic middleware:

const logError = err => { console.log("ERROR: " + String(err)) } const errorLoggerMiddleware = (err, req, res, next) => { logError(err) next(err) } const returnErrorMiddleware = (err, req, res, next) => { res.status(err.statusCode || 500) .send(err.message) } module.exports = { logError, errorLoggerMiddleware, returnErrorMiddleware }

然后,您可以在您的应用程序中使用此中间件,如下所示:

 const { errorLoggerMiddleware, returnErrorMiddleware } = require('./errorMiddleware') app.use(errorLoggerMiddleware) app.use(returnErrorMiddleware)

您现在可以在中间件中定义自定义逻辑以适当地处理错误。 您不再需要担心在整个代码库中实现单个错误处理结构。

6. 重新启动您的应用程序以处理程序员错误(Node.js)

当 Node.js 应用程序遇到程序员错误时,它们可能不一定会抛出异常并尝试关闭应用程序。 此类错误可能包括由程序员错误引起的问题,例如高 CPU 消耗、内存膨胀或内存泄漏。 处理这些问题的最佳方法是通过 Node.js 集群模式或 PM2 等独特工具使应用程序崩溃,从而优雅地重新启动应用程序。 这可以确保应用程序不会因用户操作而崩溃,从而呈现糟糕的用户体验。

7. 捕获所有未捕获的异常(Node.js)

您永远无法确定您已经涵盖了您的应用程序中可能出现的所有错误。 因此,实施备用策略以从您的应用程序中捕获所有未捕获的异常非常重要。

您可以这样做:

 process.on('uncaughtException', error => { console.log("ERROR: " + String(error)) // other handling mechanisms })

您还可以确定发生的错误是标准异常还是自定义操作错误。 根据结果​​,您可以退出进程并重新启动它以避免意外行为。

8. 捕获所有未处理的 Promise Rejections (Node.js)

与您永远无法涵盖所有​​可能的异常类似,您很有可能会错过处理所有可能的承诺拒绝。 但是,与异常不同,promise 拒绝不会引发错误。

因此,一个被拒绝的重要承诺可能会作为警告而溜走,并使您的应用程序面临遇到意外行为的可能性。 因此,实现一个回退机制来处理 Promise 拒绝是至关重要的。

您可以这样做:

 const promiseRejectionCallback = error => { console.log("PROMISE REJECTED: " + String(error)) } process.on('unhandledRejection', callback)
如果您创建一个应用程序,您也有可能在其中创建错误和其他问题。 在本指南的帮助下了解如何处理它们️ 点击推

概括

与任何其他编程语言一样,JavaScript 中的错误非常频繁且自然。 在某些情况下,您甚至可能需要故意抛出错误以向用户指示正确的响应。 因此,了解它们的解剖结构和类型非常重要。

此外,您需要配备正确的工具和技术来识别和防止错误导致您的应用程序崩溃。

在大多数情况下,对于所有类型的 JavaScript 应用程序来说,通过仔细执行来处理错误的可靠策略就足够了。

是否还有其他您无法解决的 JavaScript 错误? 有什么建设性地处理 JS 错误的技术吗? 在下面的评论中让我们知道!