墨菲定律指出,任何可能出错的事情最终都会出错。这在编程世界中应用得太好了。如果您创建一个应用程序,您很可能会产生错误和其他问题。JavaScript 中的错误就是这样一个常见问题!
Kinsta 的托管服务获得了数百个五星级评级。每天。
真的很喜欢 Kinsta 的实时聊天工程师提供的经验水平和支持。24/7 支持是大型网站的游戏规则改变者,尤其是电子商务。
利兰·萨伦巴
找出为什么
软件产品的成功取决于其创建者在伤害用户之前解决这些问题的能力。在所有编程语言中,JavaScript 因其平均错误处理设计而臭名昭著。
如果您正在构建一个 JavaScript 应用程序,那么您很有可能会在某一时刻弄乱数据类型。如果不是这样,那么您最终可能会将 undefined 替换为 null 或将三等号运算符 (===) 替换为双等号运算符 (==)。
犯错是人之常情。这就是为什么我们将向您展示您需要了解的有关处理 JavaScript 错误的所有信息。
本文将引导您了解 JavaScript 中的基本错误,并解释您可能遇到的各种错误。然后,您将学习如何识别和修复这些错误。还有一些技巧可以在生产环境中有效地处理错误。
事不宜迟,让我们开始吧!
什么是 JavaScript 错误?
编程错误是指程序无法正常运行的情况。当程序不知道如何处理手头的工作时,可能会发生这种情况,例如在没有网络连接的情况下尝试打开不存在的文件或访问基于 Web 的 API 端点时。
这些情况促使程序向用户抛出错误,说明它不知道如何继续。该程序收集尽可能多的有关错误的信息,然后报告它无法继续前进。
聪明的程序员试图预测和覆盖这些场景,这样用户就不必独立地找出像“404”这样的技术错误消息。相反,它们显示了一条更容易理解的信息:“找不到该页面。”
JavaScript 中的错误是在发生编程错误时显示的对象。这些对象包含有关错误类型、导致错误的语句以及发生错误时的堆栈跟踪的大量信息。JavaScript 还允许程序员创建自定义错误,以便在调试问题时提供额外信息。
错误的属性
现在 JavaScript 错误的定义已经很清楚了,是时候深入研究细节了。
JavaScript 中的错误带有某些标准和自定义属性,有助于理解错误的原因和影响。默认情况下,JavaScript 中的错误包含三个属性:
- message:携带错误信息的字符串值
- 名称:发生的错误类型(我们将在下一节深入探讨)
- stack:发生错误时执行的代码的堆栈跟踪。
此外,error 还可以携带 columnNumber、lineNumber、fileName 等属性,以更好地描述错误。但是,这些属性不是标准的,可能会出现在 JavaScript 应用程序生成的每个错误对象中,也可能不会出现。
了解堆栈跟踪
堆栈跟踪是发生异常或警告等事件时程序所在的方法调用列表。这是伴随异常的示例堆栈跟踪的样子:
堆栈跟踪示例。
如您所见,它首先打印错误名称和消息,然后是被调用的方法列表。每个方法调用都说明其源代码的位置以及调用它的行。您可以使用这些数据浏览您的代码库并确定是哪段代码导致了错误。
此方法列表以堆叠方式排列。它显示了您的异常首次引发的位置以及它如何通过堆叠的方法调用传播。为异常实现捕获不会让它通过堆栈向上传播并使您的程序崩溃。但是,您可能希望在某些情况下故意不捕获致命错误以使程序崩溃。
错误与异常
大多数人通常将错误和异常视为同一件事。但是,必须注意它们之间的细微但根本的区别。
异常是已抛出的错误对象。
为了更好地理解这一点,让我们举一个简单的例子。以下是如何在 JavaScript 中定义错误:
const wrongTypeError = TypeError(“发现错误类型,预期字符”)
这就是 wrongTypeError 对象变成异常的方式:
抛出错误类型错误
然而,大多数人倾向于使用在抛出错误对象时定义错误对象的简写形式:
throw TypeError(“发现错误类型,预期字符”)
这是标准做法。然而,这也是开发人员倾向于混淆异常和错误的原因之一。因此,即使您使用速记来快速完成工作,了解基础知识也至关重要。
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 “发生错误”
但是,抛出原始数据类型不会提供有关错误的详细信息,例如其类型、名称或随附的堆栈跟踪。为了解决这个问题并标准化错误处理过程,我们提供了 Error 类。也不鼓励在抛出异常时使用原始数据类型。
您可以扩展 Error 类来创建您的自定义错误类。以下是如何执行此操作的基本示例:
类 ValidationError 扩展错误 {
构造函数(消息) {
超级(消息);
this.name = “验证错误”;
}
}
您可以通过以下方式使用它:
throw ValidationError(“找不到属性:名称”)
然后您可以使用 instanceof 关键字来识别它:
try {
validateForm() // 抛出 ValidationError 的代码
} catch (e) {
if (e instanceof ValidationError)
// 做其他
事情
// 做其他事情
}
JavaScript 中最常见的 10 个错误
现在您已经了解了常见的错误类型以及如何创建自定义错误类型,是时候看看您在编写 JavaScript 代码时会遇到的一些最常见的错误了。
1. 未捕获的 RangeError
在几种不同的情况下,Google Chrome 中会出现此错误。首先,如果您调用递归函数并且它不会终止,则可能会发生这种情况。您可以在 Chrome 开发者控制台中自行查看:
带有递归函数调用的 RangeError 示例。
因此,要解决此类错误,请确保正确定义递归函数的边界情况。发生此错误的另一个原因是您传递的值超出了函数的参数范围。这是一个例子:
带有 toExponential() 调用的 RangeError 示例。
错误消息通常会指出您的代码有什么问题。一旦你做出改变,它就会得到解决。
toExponential() 函数调用的输出。
2. Uncaught TypeError: Cannot set property
当您在未定义的引用上设置属性时会发生此错误。您可以使用此代码重现该问题:
var list
list.count = 0
这是您将收到的输出:
类型错误示例。
要修复此错误,请在访问其属性之前使用值初始化引用。以下是修复后的外观:
如何修复类型错误。
3. Uncaught TypeError: Cannot read property
这是 JavaScript 中最常出现的错误之一。当您尝试读取属性或调用未定义对象的函数时,会发生此错误。您可以通过在 Chrome 开发人员控制台中运行以下代码来非常轻松地重现它:
var func
func.call ()
这是输出:
带有未定义函数的 TypeError 示例。
未定义的对象是导致此错误的众多可能原因之一。此问题的另一个突出原因可能是在呈现 UI 时未正确初始化状态。这是来自 React 应用程序的真实示例:
从“反应”中导入反应,{ useState,useEffect };
常量 CardsList = () => {
常量 [状态,setState] = useState();
useEffect(() => {
setTimeout(() => setState({ items: [“Card 1”, “Card 2”] }), 2000);
}, []);
返回 (
<>
{state.items.map((item) => (
<li key={item}>{item}</li>
))}
</>
);
};
导出默认 CardsList;
该应用程序从一个空状态容器开始,并在 2 秒延迟后提供一些项目。延迟用于模拟网络调用。即使您的网络速度非常快,您仍然会面临轻微的延迟,因为该组件将至少呈现一次。如果您尝试运行此应用程序,您将收到以下错误:
浏览器中的 TypeError 堆栈跟踪。
这是因为,在渲染时,状态容器是未定义的;因此,上面不存在任何属性项。修复这个错误很容易。您只需要为状态容器提供初始默认值。
// …
const [state, setState] = useState({items: []});
// …
现在,在设置延迟之后,您的应用程序将显示类似的输出:
代码输出。
您的代码中的确切修复可能会有所不同,但这里的本质是在使用它们之前始终正确初始化您的变量。
4. TypeError: ‘undefined’ is not an object
当您尝试访问未定义对象的属性或调用未定义对象的方法时,会在 Safari 中发生此错误。您可以从上面运行相同的代码来自己重现错误。
带有未定义函数的 TypeError 示例。
这个错误的解决方法也是一样的——确保你已经正确地初始化了你的变量,并且在访问一个属性或方法时它们不是未定义的。
5. TypeError: null is not an object
这又与前面的错误相似。它发生在 Safari 上,这两个错误之间的唯一区别是,当正在访问其属性或方法的对象为 null 而不是 undefined 时,会抛出这个错误。您可以通过运行以下代码来重现这一点:
变量函数 = 空
func.call()
这是您将收到的输出:
带有 null 函数的 TypeError 示例。
因为 null 是显式设置为变量的值,而不是由 JavaScript 自动分配的值。仅当您尝试访问自己设置为 null 的变量时,才会发生此错误。因此,您需要重新访问您的代码并检查您编写的逻辑是否正确。
6. TypeError: 无法读取属性’length’
当您尝试读取 null 或未定义对象的长度时,Chrome 会出现此错误。这个问题的原因和前面的问题类似,但是在处理列表的时候出现的频率比较高;因此值得特别提及。以下是重现问题的方法:
带有未定义对象的 TypeError 示例。
但是,在较新版本的 Chrome 中,此错误报告为 Uncaught TypeError: Cannot read properties of undefined。这是它现在的样子:
在较新的 Chrome 版本上带有未定义对象的 TypeError 示例。
再次,修复是确保您尝试访问其长度的对象存在并且未设置为空。
7. TypeError: ‘undefined’ is not a function
当您尝试调用脚本中不存在的方法或该方法存在但无法在调用上下文中引用时,会发生此错误。这个错误通常发生在谷歌浏览器中,您可以通过检查抛出错误的代码行来解决它。如果您发现拼写错误,请修复它并检查它是否能解决您的问题。
如果您在代码中使用了自引用关键字 this,如果 this 没有适当地绑定到您的上下文,则可能会出现此错误。考虑以下代码:
函数 showAlert() {
alert(“这里的信息”)
}
document.addEventListener(“click”, () => {
this.showAlert();
})
如果你执行上面的代码,它会抛出我们讨论过的错误。发生这种情况是因为作为事件侦听器传递的匿名函数正在文档的上下文中执行。
相反,函数 showAlert 是在窗口的上下文中定义的。
要解决这个问题,您必须通过使用 bind() 方法绑定函数来传递对函数的正确引用:
document.addEventListener(“点击”, this.showAlert.bind(this))
8. ReferenceError: event is not defined
当您尝试访问未在调用范围内定义的引用时,会发生此错误。这通常在处理事件时发生,因为它们通常会在回调函数中为您提供一个名为 event 的引用。如果您忘记在函数的参数中定义事件参数或拼写错误,则可能会发生此错误。
订阅时事通讯
想知道我们是如何将流量提高到 1000% 以上的吗?
加入 20,000 多人的行列,他们会通过 WordPress 内幕技巧获得我们的每周时事通讯!
现在订阅
此错误可能不会在 Internet Explorer 或 Google Chrome 中发生(因为 IE 提供了一个全局事件变量并且 Chrome 会自动将事件变量附加到处理程序),但它可能会在 Firefox 中发生。所以建议留意这样的小错误。
9. TypeError:赋值给常量变量
这是由于粗心造成的错误。如果您尝试将新值分配给常量变量,您将遇到这样的结果:
带有常量对象分配的 TypeError 示例。
虽然现在看起来很容易修复,但想象一下数百个这样的变量声明,其中一个被错误地定义为 const 而不是 let!与 PHP 等其他脚本语言不同,在 JavaScript 中声明常量和变量的风格差别很小。因此,当您遇到此错误时,建议首先检查您的声明。如果您忘记上述引用是一个常量并将其用作变量,您也可能会遇到此错误。这表明您的应用程序逻辑存在粗心或缺陷。尝试解决此问题时,请务必检查此项。
10.(未知):脚本错误
当第三方脚本向您的浏览器发送错误时,就会发生脚本错误。此错误后跟(未知),因为第三方脚本与您的应用属于不同的域。浏览器隐藏了其他细节,以防止第三方脚本泄露敏感信息。
在不了解完整详细信息的情况下,您无法解决此错误。您可以执行以下操作来获取有关该错误的更多信息:
- 在脚本标签中添加 crossorigin 属性。
- 在托管脚本的服务器上设置正确的 Access-Control-Allow-Origin 标头。
- [可选] 如果您无权访问托管脚本的服务器,您可以考虑使用代理将您的请求中继到服务器并返回到带有正确标头的客户端。
一旦您可以访问错误的详细信息,您就可以着手解决问题,这可能与第三方库或网络有关。
如何识别和防止 JavaScript 中的错误
虽然上面讨论的错误是 JavaScript 中最常见和最常见的错误,但您会遇到,仅仅依靠几个示例是远远不够的。在开发 JavaScript 应用程序时,了解如何检测和防止任何类型的错误至关重要。以下是如何处理 JavaScript 中的错误。
手动抛出和捕获错误
处理手动或运行时抛出的错误的最基本方法是捕获它们。与大多数其他语言一样,JavaScript 提供了一组关键字来处理错误。在着手处理 JavaScript 应用程序中的错误之前,必须深入了解它们中的每一个。
扔
该集合的第一个也是最基本的关键字是 throw。很明显,throw 关键字用于在 JavaScript 运行时中手动抛出错误以创建异常。我们已经在本文前面讨论过这个问题,这里是这个关键字意义的要点:
- 你可以抛出任何东西,包括数字、字符串和错误对象。
- 但是,不建议抛出诸如字符串和数字之类的原始数据类型,因为它们不携带有关错误的调试信息。
- 示例: throw TypeError(“请提供一个字符串”)
尝试
try 关键字用于指示代码块可能会引发异常。它的语法是:
try {
// 这里有错误的代码
}
需要注意的是,catch 块必须始终跟在 try 块之后才能有效地处理错误。
抓住
catch 关键字用于创建一个 catch 块。此代码块负责处理尾随 try 块捕获的错误。这是它的语法:
catch (exception) {
// 此处处理异常的代码
}
这就是一起实现 try 和 catch 块的方式:
try {
// 业务逻辑代码
} catch (exception) {
// 错误处理代码
}
与 C++ 或 Java 不同,您不能将多个 catch 块附加到 JavaScript 中的 try 块。这意味着您不能这样做:
try {
// 业务逻辑代码
} catch (exception) {
if (exception instanceof TypeError) {
// 做某事
}
} catch (exception) {
if (exception instanceof RangeError) {
// 做某事
}
}
相反,您可以在单个 catch 块中使用 if…else 语句或 switch case 语句来处理所有可能的错误情况。它看起来像这样:
try {
// 业务逻辑代码
} catch (exception) {
if (exception instanceof TypeError) {
// do something
} else if (exception instanceof RangeError) {
// do something else
}
}
finally
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(“错误发生:” + event)
}
但是,您可以使用此功能为您的应用创建全局错误处理机制。以下是您的操作方法:
window.onerror = (event) => {
console.log(“错误发生:” + event)
}
使用此事件处理程序,您可以摆脱代码中的多个 try…catch 块,并集中您的应用程序的错误处理,类似于事件处理。您可以将多个错误处理程序附加到窗口,以维护 SOLID 设计原则中的单一责任原则。解释器将循环遍历所有处理程序,直到到达适当的处理程序。
通过回调传递错误
虽然简单和线性函数允许错误处理保持简单,但回调会使事情复杂化。
考虑以下代码:
需要一个可以为您提供竞争优势的托管解决方案?Kinsta 为您提供令人难以置信的速度、最先进的安全性和自动缩放功能。查看我们的计划
const calculateCube = (number, callback) => {
setTimeout(() => {
const cube = number * number * number
callback(cube)
}, 1000)
}
常量回调 = 结果 => 控制台日志(结果)
计算立方(4,回调)
上面的函数演示了一个异步条件,其中一个函数需要一些时间来处理操作并稍后在回调的帮助下返回结果。
如果您尝试在函数调用中输入字符串而不是 4,您将得到 NaN 作为结果。
这需要妥善处理。就是这样:
const calculateCube = (数字, 回调) => {
setTimeout(() => {
if (typeof number !== “number”)
throw new Error(“Numeric argument is expected”)
const cube = number * number * number
callback(cube)
}, 1000)
}
常量回调 = 结果 => 控制台日志(结果)
尝试 {
calculateCube(4, callback)
} catch (e) { console.log(e) }
这应该可以理想地解决问题。但是,如果您尝试将字符串传递给函数调用,您将收到以下信息:
错误参数的错误示例。
即使您在调用函数时实现了 try-catch 块,它仍然表示错误未捕获。由于超时延迟,在执行 catch 块后抛出错误。
这可能在网络调用中很快发生,在这种情况下会出现意外延迟。您需要在开发应用程序时涵盖此类情况。
以下是在回调中正确处理错误的方法:
const calculateCube = (数字, 回调) => {
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)
}
尝试 {
calculateCube(‘hey’, callback)
} catch (e) {
console.log(e)
}
现在,控制台的输出将是:
带有非法参数的 TypeError 示例。
这表明错误已得到适当处理。
处理 Promise 中的错误
大多数人倾向于使用 Promise 来处理异步活动。Promise 还有另一个优点——被拒绝的 Promise 不会终止你的脚本。但是,您仍然需要实现一个 catch 块来处理 Promise 中的错误。为了更好地理解这一点,让我们使用 Promises 重写 calculateCube() 函数:
常量延迟 = ms => 新承诺(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
}
尝试 {
calculateCube(4).then(r => console.log(r))
} 捕捉 (e) { console.log(e) }
前面代码中的超时已经被隔离到延迟函数中以便理解。如果您尝试输入一个字符串而不是 4,您获得的输出将类似于以下内容:
在 Promise 中带有非法参数的 TypeError 示例。
同样,这是由于 Promise 在其他所有内容完成执行后抛出错误。这个问题的解决方案很简单。只需像这样向 Promise 链添加一个 catch() 调用:
计算Cube(“嘿”)
.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 中的错误的最优选方式是链式 catch,最后连接到 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 存储库跟踪更改
由于代码库中的手动错误,经常会出现许多错误。在开发或调试代码时,您最终可能会进行不必要的更改,这可能会导致代码库中出现新的错误。自动化测试是在每次更改后检查代码的好方法。但是,它只能告诉您是否有问题。如果你不经常备份你的代码,你最终会浪费时间试图修复一个以前运行良好的函数或脚本。
这就是 git 发挥作用的地方。通过适当的提交策略,您可以使用 git 历史记录作为备份系统来查看代码在开发过程中的演变过程。您可以轻松浏览旧提交并找出该函数的版本之前运行良好,但在不相关的更改后抛出错误。
然后,您可以恢复旧代码或比较两个版本以确定哪里出了问题。现代 Web 开发工具(如 GitHub Desktop 或 GitKraken)可帮助您并排可视化这些更改并快速找出错误。
一个可以帮助您减少错误的习惯是在您对代码进行重大更改时运行代码审查。如果您在一个团队中工作,您可以创建一个拉取请求并让团队成员彻底审查它。这将帮助您使用第二双眼睛来发现您可能遗漏的任何错误。
处理 JavaScript 错误的最佳实践
上述方法足以帮助您为下一个 JavaScript 应用程序设计一个健壮的错误处理方法。但是,最好在实施时记住一些事情,以充分利用您的防错功能。这里有一些提示可以帮助您。
1. 处理操作异常时使用自定义错误
我们在本指南的前面介绍了自定义错误,让您了解如何根据应用程序的独特情况自定义错误处理。建议尽可能使用自定义错误而不是通用 Error 类,因为它为调用环境提供了有关错误的更多上下文信息。
最重要的是,自定义错误允许您调整错误在调用环境中的显示方式。这意味着您可以根据需要选择隐藏特定详细信息或显示有关错误的其他信息。
您可以根据需要格式化错误内容。这使您可以更好地控制错误的解释和处理方式。
2.不要吞下任何例外
即使是最资深的开发人员也经常犯一个新手错误——在他们的代码中使用异常级别。
您可能会遇到有一段代码可以选择运行的情况。如果它有效,那就太好了;如果没有,你不需要做任何事情。
在这些情况下,通常很想将这段代码放在 try 块中,并在其上附加一个空的 catch 块。但是,通过这样做,您将使那段代码保持开放状态,从而导致任何类型的错误并逃脱惩罚。如果你有一个庞大的代码库和许多这样糟糕的错误管理结构的实例,这可能会变得很危险。
处理异常的最佳方法是确定所有异常都将被处理的级别并将它们提高到那里。此级别可以是控制器(在 MVC 架构应用程序中)或中间件(在传统的面向服务器的应用程序中)。
通过这种方式,您将了解在哪里可以找到应用程序中发生的所有错误并选择如何解决它们,即使这意味着不对它们做任何事情。
3. 对日志和错误警报使用集中策略
记录错误通常是处理错误的一个组成部分。那些未能制定集中策略来记录错误的人可能会错过有关其应用程序使用情况的宝贵信息。
应用程序的事件日志可以帮助您找出有关错误的关键数据并帮助快速调试它们。如果您在应用程序中设置了适当的警报机制,您可以在错误到达大部分用户群之前知道应用程序何时发生错误。
建议使用预先构建的记录器或创建一个以满足您的需求。您可以配置此记录器以根据其级别(警告、调试、信息等)处理错误,一些记录器甚至可以立即将日志发送到远程记录服务器。通过这种方式,您可以观察应用程序的逻辑在活动用户中的执行情况。
4. 适当地通知用户错误
在定义错误处理策略时要牢记的另一个要点是牢记用户。
所有干扰您的应用程序正常运行的错误都必须向用户显示可见的警报,以通知他们出现问题,以便用户可以尝试制定解决方案。如果您知道错误的快速修复方法,例如重试操作或注销并重新登录,请务必在警报中提及它以帮助实时修复用户体验。
如果错误不会对日常用户体验造成任何干扰,您可以考虑抑制警报并将错误记录到远程服务器以供以后解决。
5. 实现一个中间件(Node.js)
Node.js 环境支持中间件向服务器应用程序添加功能。您可以使用此功能为您的服务器创建错误处理中间件。
使用中间件的最大好处是所有错误都集中在一个地方处理。您可以轻松选择启用/禁用此设置以进行测试。
以下是创建基本中间件的方法:
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))
// 其他处理机制
})
您还可以确定发生的错误是标准异常还是自定义操作错误。根据结果,您可以退出进程并重新启动它以避免意外行为。
8. 捕获所有未处理的 Promise Rejections (Node.js)
与您永远无法涵盖所有可能的异常类似,您很有可能会错过处理所有可能的承诺拒绝。但是,与异常不同,promise 拒绝不会引发错误。
因此,一个被拒绝的重要承诺可能会作为警告而溜走,并使您的应用程序面临遇到意外行为的可能性。因此,实现一个回退机制来处理 Promise 拒绝是至关重要的。
您可以这样做:
const promiseRejectionCallback = error => {
console.log(“PROMISE REJECTED:” + String(error))
}
process.on(‘unhandledRejection’, callback)
如果您创建一个应用程序,您也有可能在其中创建错误和其他问题。😅 在本指南的帮助下了解如何处理它们⬇️点击推文摘
要
与任何其他编程语言一样,JavaScript 中的错误非常频繁且自然。在某些情况下,您甚至可能需要故意抛出错误以向用户指示正确的响应。因此,了解它们的解剖结构和类型非常重要。
此外,您需要配备正确的工具和技术来识别和防止错误导致您的应用程序崩溃。
在大多数情况下,对于所有类型的 JavaScript 应用程序来说,通过仔细执行来处理错误的可靠策略就足够了。
是否还有其他您无法解决的 JavaScript 错误?有什么建设性地处理 JS 错误的技术吗?在下面的评论中让我们知道!
通过以下方式节省时间、成本并最大限度地提高站点性能:
- 来自 WordPress 托管专家的即时帮助,24/7。
- Cloudflare 企业集成。
- 全球受众覆盖全球 29 个数据中心。
- 使用我们内置的应用程序性能监控进行优化。
所有这些以及更多,在一个没有长期合同、协助迁移和 30 天退款保证的计划中。查看我们的计划或与销售人员交谈以找到适合您的计划。