记录生活
简单不先于复杂

处理JavaScript错误的权威指南

6dbe899469b537c

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

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

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

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

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

  1. 什么是JavaScript错误?
  2. JavaScript中的错误类型
  3. 创建自定义错误类型
  4. JavaScript 中最常见的10个错误
  5. 如何识别和防止JavaScript中的错误
  6. 处理JavaScript错误的最佳实践

什么是JavaScript错误?

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

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

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

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

错误的属性

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

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

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

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

了解堆栈跟踪

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

4a892e3dbb8b641

堆栈跟踪示例

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

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

错误与异常

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

异常是已抛出的错误对象。

为了更好地理解这一点,让我们举一个简单的例子。以下是如何在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发生的一些常见原因是:

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

语法错误

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

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

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

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

类型错误

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

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

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

内部错误

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

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

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

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

URI错误

URIError发生在全局URI处理函数如decodeURIComponent被非法使用时。它通常表示传递给方法调用的参数不符合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. Uncaught RangeError

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

5f7ea9e38fb0619

带有递归函数调用的RangeError示例

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

6562b924d6db83a

带有toExponential()调用的RangeError示例

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

4b80617f0f1f7ed

toExponential() 函数调用的输出

2. Uncaught TypeError: Cannot set property

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

var list
list.count = 0

这是您将收到的输出:

bb34f1fc82a8a43

类型错误示例

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

c7cff60cb52f698

如何修复类型错误

3. Uncaught TypeError: Cannot read property

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

var func
func.call()

这是输出:

f456c05f3eb5a24

带有未定义函数的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秒后提供一些项目。延迟用于模拟网络调用。即使您的网络速度非常快,您仍然会面临轻微的延迟,因为该组件将至少呈现一次。如果您尝试运行此应用程序,您将收到以下错误:

b3b86486210e6fa

浏览器中的TypeError堆栈跟踪

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

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

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

a1c7727c4f8d8f4

代码输出

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

4. TypeError: ‘undefined’ is not an object

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

450ccf5151fbeeb

带有未定义函数的TypeError示例

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

5. TypeError: null is not an object

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

var func = null
func.call()

这是您将收到的输出:

c27252fe4052072

带有null函数的TypeError示例

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

6. TypeError: Cannot read property ‘length’

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

6fcf2d5d5112b00

带有未定义对象的TypeError示例

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

730187ac3cc58fa

在较新的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: Assignment to constant variable

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

56c2414d3937367

带有常量对象分配的TypeError示例

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

10.(unknown): Script error

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

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

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

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

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

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

手动抛出和捕获错误

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

throw

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

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

try

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

try {
// error-prone code here
}

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

catch

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

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设计原则中的单一责任原则。解释器将循环遍历所有处理程序,直到到达适当的处理程序。

通过回调传递错误

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

考虑以下代码:

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

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

d5736802a4a0595

错误参数的错误示例

即使您在调用函数时实现了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)
}

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

1e2d87c87364086

带有非法参数的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,您获得的输出将类似于以下内容:

ac9c6f059313aa8

在Promise中带有非法参数的TypeError示例

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

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

现在输出将是:

2b6c082a81f1a4f

处理带有非法参数的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:哪种方法更佳?

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

throw/catch

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

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

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

onerror()

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

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

Callbacks and Promises

回调和承诺中的错误处理因代码设计和结构而异。但是,如果您在编写代码之前在这两者之间进行选择,最好使用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))
// other handling mechanisms
})

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

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

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

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

您可以这样做:

const promiseRejectionCallback = error => {
console.log("PROMISE REJECTED: " + String(error))
}
process.on('unhandledRejection', callback)

小结

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

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

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

赞(0)
未经允许不得转载:爱安普 » 处理JavaScript错误的权威指南