处理JavaScript错误的权威指南
墨菲定律指出,任何可能出错的事情最终都会出错。这在编程领域应用得非常好。如果创建一个应用程序,很可能会产生错误和其他问题。JavaScript中的错误是如此普遍的问题!
一个产品的成功取决于它的创造者在伤害用户之前解决这些问题的能力。在所有编程语言中,JavaScript因其一般的错误处理设计而臭名昭著。
如果您正在构建一个JavaScript应用程序,您可能会在某个时候弄乱数据类型。如果不是这样,那么您可能最终会用null替换undefined,或者用双等号运算符(= =)替换三等号运算符(= = =)。
犯错是人之常情。这就是为什么我们将向您展示您需要知道的关于处理JavaScript错误的一切。
本文将引导您了解JavaScript中的基本错误,并解释您可能会遇到的各种错误。然后,您将学习如何识别和修复这些错误。还有一些技术可以有效地处理生产环境中的错误。
什么是JavaScript错误?
JavaScript中的错误类型
创建自定义错误类型
JavaScript中最常见的10个错误
如何识别和防止JavaScript中的错误
处理JavaScript错误的最佳实践
什么是JavaScript错误?
编程错误是指程序不能正常运行的情况。当程序不知道如何处理手头的工作时,例如试图打开不存在的文件或在没有网络连接的情况下访问基于Web的API端点时,就会发生这种情况。
这些情况会提示程序向用户抛出一个错误,表明它不知道如何继续。程序收集尽可能多的错误信息,然后报告它无法继续运行。
聪明的程序员试图预测并覆盖这些场景,这样用户就不必独立找出类似“404”这样的技术错误信息。相反,它们会显示一条更容易理解的消息:“找不到该页面。”
JavaScript中的错误是发生编程错误时显示的对象。这些对象包含大量关于错误类型、导致错误的语句以及错误发生时的堆栈跟踪的信息。JavaScript还允许程序员创建自定义错误,以便在调试问题时提供附加信息。
的错误属性
现在JavaScript错误的定义已经很清楚了,是时候深入研究细节了。
JavaScript中的错误有一些标准的和自定义的属性,有助于理解错误的原因和影响。默认情况下,JavaScript中的错误包含三个属性:
Message:携带错误信息的字符串值。
名称:发生的错误类型(我们将在下一节深入探讨)
Stack:出错时执行的代码的堆栈跟踪。
此外,错误还可以携带列号、行号、文件名等属性。为了更好地描述错误。但是,这些属性不是标准的,可能出现在JavaScript应用程序生成的每个错误对象中,也可能不出现。
了解堆栈跟踪
堆栈跟踪是发生异常或警告时程序所在的方法调用列表。伴随异常的示例堆栈跟踪如下所示:
堆栈跟踪示例
如您所见,它首先打印错误名称和消息,然后打印被调用方法的列表。每个方法调用都描述了其源代码的位置以及调用它的代码行。您可以使用这些数据来浏览您的代码库,并确定哪个代码段导致了错误。
这个方法列表以堆叠的方式排列。它显示了您的异常第一次被抛出的位置,以及它是如何通过堆栈方法调用传播的。捕捉异常不会让它沿堆栈向上传播并使你的程序崩溃。但是,在某些情况下,您可能想要故意不捕捉致命错误,以使程序崩溃。
错误和异常
大多数人通常将错误和异常视为同一件事。但是,我们必须注意它们之间微妙但根本的区别。
异常是已经抛出的错误对象。
为了更好地理解这一点,让我们举一个简单的例子。以下是如何在JavaScript中定义错误:
const wrongTypeError = TypeError(“Wrong type found, expected character”)
这就是错误类型错误对象成为异常的原因:
throw wrongTypeError
然而,大多数人在投掷时倾向于使用定义错误对象的简短形式:
throw TypeError(“Wrong type found, expected character”)
这是标准做法。然而,这也是开发人员容易混淆异常和错误的原因之一。所以,即使你用速记快速完成工作,了解基础知识也是必不可少的。
JavaScript中的错误类型
JavaScript中有一系列预定义的错误类型。只要程序员没有显式处理应用程序中的错误,它们就会被JavaScript运行时自动选择和定义。
本节将引导您了解JavaScript中一些最常见的错误类型,并找出它们发生的时间和原因。
范围误差
当变量设置超出其合法值范围时,将引发RangeError。当一个值作为参数传递给一个函数,而给定的值不在该函数的参数范围内时,通常会发生这种情况。当使用文档质量差的第三方库时,修复它有时会很棘手,因为您需要知道参数的可能值的范围以传递正确的值。
发生错误的一些常见情况是:
试图通过数组构造函数创建长度非法的数组。
将错误值传递给数值方法,如toexponential()、toeprecision()、tofixed()等。
将非法值传递给字符串函数,如normalize()。
参考误差
当代码中的变量引用有问题时,会出现ReferenceError。您可能会忘记在使用变量之前定义变量的值,或者您可能会试图在代码中使用不可访问的变量。在任何情况下,堆栈跟踪都提供了足够的信息来查找和修复错误的变量引用。
ReferenceErrors的一些常见原因是:
在变量名中键入了错误的单词。
试图访问其作用域之外的块作用域变量。
在加载之前从外部库中引用全局变量(例如,来自jQuery的$ 1)。
语法错误
这些错误是最容易修复的,因为它们指出了代码语法中的错误。因为JavaScript是一种解释而不是编译的脚本语言,所以当应用程序执行包含错误的脚本时,这些脚本语言将被抛出。在编译语言的情况下,这样的错误将在编译过程中被识别。因此,在修复之前不会创建应用程序二进制文件。
可能出现语法错误的一些常见原因是:
缺少引号
缺少右括号
花括号或其他字符的对齐不正确
最好使用IDE中的林挺工具在这些错误出现在浏览器中之前识别它们。
类型错误
类型是JavaScript应用程序中最常见的错误之一。当某些值不是特定的预期类型时,就会产生这个错误。以下是一些常见的情况:
不是调用方法的对象。
试图访问空对象,或者该对象的属性未定义。
将字符串视为数字,反之亦然。
TypeError有许多可能性。我们稍后将查看一些著名的示例,并学习如何修复它们。
内部错误
当JavaScript运行时引擎中出现异常时,使用InternalError类型。它可能表示也可能不表示您的代码有问题。
通常,内部错误只在两种情况下发生:
当JavaScript运行时的补丁或更新出现引发异常的错误时(这种情况很少发生)
当你的代码包含对JavaScript引擎来说太大的实体时(例如,太多的开关,太大的数组初始化器,太多的递归)
解决这个错误的最合适的方法是通过错误消息确定原因,如果可能的话重构应用程序逻辑,以消除JavaScript引擎上突然激增的工作负载。
URI误差
非法使用decodeURIComponent等全局URI处理函数时会出现URIError。它通常表示传递给方法调用的参数不符合URI标准,因此方法无法正确解析这些参数。
通常很容易诊断这些错误,因为您只需要检查参数是否变形。
评估误差
当调用函数eval()时发生错误时,将发生EvalError。eval()函数用于执行存储在字符串中的JavaScript代码。但是由于安全问题,强烈建议不要使用eval()函数,而且目前的ECMAScript规范已经不再抛出EvalError类,所以这种错误类型的存在只是为了保持与旧JavaScript代码的向后兼容。
如果您使用的是旧版本的JavaScript,您可能会遇到此错误。反正最好调查一下eval()函数调用中执行的代码有没有异常。
创建自定义错误类型
尽管JavaScript提供了足够多的错误类型列表来涵盖大多数场景,但是如果列表不能满足您的需求,您总是可以创建新的错误类型。这种灵活性的基础是JavaScript允许您使用throw命令逐字抛出任何内容。
因此,从技术上讲,这些说法是完全合法的:
throw 8throw “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 somethingelse// do something else}
JavaScript中最常见的10个错误
既然您已经知道了常见的错误类型以及如何创建自定义的错误类型,那么是时候看看在编写JavaScript代码时会遇到的一些最常见的错误了。
1.未捕获的范围错误
谷歌Chrome在几种不同情况下都会出现这个错误。首先,如果你调用一个递归函数并且它没有终止,这可能会发生。你可以在Chrome开发者控制台中亲自查看:
递归函数调用的RangeError示例
因此,要解决这类错误,请确保递归函数的边界条件定义正确。此错误的另一个原因是您传递的值超出了函数的参数范围。这是一个例子:
toExponential()调用的RangeError示例
错误通常表明您的代码有什么问题。一旦你做出改变,问题就解决了。
toExponential()函数调用的输出
2.未捕获的类型错误:无法设置属性
当您在未定义的引用上设置属性时,会出现此错误。您可以使用以下代码重现该问题:
var listlist.count = 0
这是您将收到的输出:
类型错误示例
若要修复此错误,请在访问引用的属性之前用一个值初始化该引用。下面是修复后的样子:
如何修复类型错误
3.未捕获的类型错误:无法读取属性
这是JavaScript中最常见的错误之一。当您尝试读取属性或调用未定义对象的函数时,会发生此错误。通过在Chrome开发人员控制台中运行以下代码,您可以非常容易地重现它:
var funcfunc.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) => ({item}))});};export default CardsList;
应用程序以空状态容器启动,并在延迟2秒后提供一些项目。用于延迟模拟网络呼叫。即使您的网络非常快,您仍然会面临轻微的延迟,因为组件至少会出现一次。如果您尝试运行此应用程序,将会收到以下错误:
在浏览器中键入错误堆栈跟踪
这是因为,在渲染时,状态容器是未定义的;因此,它没有任何属性项。纠正这个错误很容易。您只需要为状态容器提供初始默认值。
// …const [state, setState] = useState({items: []});// …
现在,设置延迟后,您的应用程序将显示类似的输出:
代码输出
代码中的确切修复可能会有所不同,但这里的本质是在使用变量之前总是正确地初始化变量。
4.type error:“undefined”不是对象
当您尝试访问未定义对象的属性或调用未定义对象的方法时,Safari中会出现此错误。您可以运行上面的相同代码来重现错误。
未定义函数的TypeError示例
这个错误的解决方案是一样的——确保您已经正确地初始化了变量,并且在访问属性或方法时它们没有被定义。
5.TypeError: null不是对象
这和前面的错误类似。它发生在Safari上,这两个错误之间的唯一区别是,当其属性或方法被访问的对象为null时,它不是未定义的。您可以通过运行以下代码来重现这种情况:
var func = nullfunc.call()
这是您将收到的输出:
具有空函数的TypeError示例
因为null是显式设置为变量的值,而不是JavaScript自动赋值的值。只有当您试图访问由null本身设置的变量时,才会发生此错误。因此,您需要重新审视您的代码,检查您编写的逻辑是否正确。
6.TypeError:无法读取属性“length”
当你试图读取一个空的或者未定义的对象的长度时,这个错误就会在Chrome中出现。这个问题的原因和上一个类似,只是在处理列表的时候出现的更频繁;因此,它值得特别一提。以下是重现该问题的方法:
具有未定义对象的TypeError示例
然而,在Chrome的新版本中,该错误被报告为uncaughttypeerror:无法读取undefined的属性。这是它现在的样子:
新Chrome版本中未定义对象的类型错误示例
第三,修复方法是确保您试图访问的长度的对象存在,并且没有设置为null。
7.类型错误:“undefined”不是函数
当您尝试调用脚本中不存在的方法,或者该方法存在但无法在调用上下文中引用时,会出现此错误。这个错误通常发生在Google Chrome中,你可以通过检查抛出错误的代码行来解决。如果你发现一个拼写错误,请改正它并检查它是否能解决你的问题。
如果在代码中使用自引用关键字this,如果this没有正确绑定到您的上下文,则可能会发生此错误。考虑以下代码:
function showAlert() {alert(“message here”)}document.addEventListener(“click”, () => {this.showAlert();})
如果执行上面的代码,就会抛出我们讨论过的错误。发生这种情况是因为作为事件侦听器传递的匿名函数正在文档的上下文中执行。
相反,showAlert函数是在window的上下文中定义的。
要解决这个问题,您必须通过使用bind()方法绑定函数来传递对该函数的正确引用:
document.addEventListener(“click”, this.showAlert.bind(this))
8.ReferenceError:事件未定义
当您试图访问调用范围中未定义的引用时,会发生此错误。这通常发生在处理事件时,因为它们通常为您提供对事件回调函数中的调用的引用。如果您忘记在函数的参数中定义事件参数,或者出现拼写错误,可能会出现此错误。
这种错误在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 {// error-prone code here}
需要注意的是,catch块必须始终跟在try块后面,以便有效地处理错误。
捕捉
catch关键字用于创建catch块。该代码块负责处理由尾随try块捕获的错误。这是它的语法:
catch (exception) {// code to handle the exception here}
这是您可以一起实现try和catch块的方式:
try {// business logic code} catch (exception) {// error handling code}
与C++或Java不同,在JavaScript中,不能将多个catch块附加到try块。这意味着您不能这样做:
try {// business logic code} catch (exception) {if (exception instanceof TypeError) {// do something}} catch (exception) {if (exception instanceof RangeError) {// do something}}
相反,您可以使用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块都将被执行。这意味着解释器将在程序崩溃之前执行finally块中的代码,即使catch块不能完全处理错误或者catch块中抛出了错误。
要被认为是有效的,JavaScript中的try块后面需要跟一个catch或finally块。没有这些,解释器将抛出一个语法错误。因此,在处理错误时,确保至少遵循try块。
使用onerror()方法全局处理错误。
onerror()方法适用于所有HTML元素,用于处理它们可能发生的任何错误。例如,如果img标签找不到指定URL的图像,它会触发其onerror方法以允许用户处理错误。onerror()方法适用于所有HTML元素,用于处理其中可能出现的任何错误。例如,如果img标记找不到指定URL的图像,它将触发其onerror方法,以允许用户处理错误。
通常,您会在onerror调用中提供另一个图像URL,以便img标记回退到。这是您可以通过JavaScript执行此操作的方法:通常,您将在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中消除多个块…捕捉代码并专注于应用程序的错误处理,类似于事件处理。您可以将多个错误处理程序附加到窗口,以维护实体设计原则中的单一责任原则。解释器将遍历所有处理程序,直到找到合适的处理程序。
通过回调传递错误
虽然简单的线性函数可以使错误处理保持简单,但是回调会使事情变得复杂。
考虑以下代码:
const calculateCube = (number, callback) => {setTimeout(() => {const cube = number * number * numbercallback(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 * numbercallback(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 * numbercallback(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示例
这表明错误已得到正确处理。
处理承诺中的错误
大多数人倾向于使用Promise来处理异步活动。承诺还有一个好处——被拒绝的承诺不会终止你的剧本。但是,您仍然需要实现一个catch块来处理Promise中的错误。为了更好地理解这一点,让我们用承诺重写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 * numberreturn cube}try {calculateCube(4).then(r => console.log(r))} catch (e) { console.log(e) }
为了便于理解,前面代码中的超时被隔离到延迟函数中。如果您尝试输入一个字符串而不是4,您得到的输出将类似于以下内容:
Promise中带有非法参数的TypeError示例
同样,这是因为Promise在其他所有事情都执行完之后抛出了一个错误。这个问题的解决方法很简单。只需将call catch()添加到承诺链中,如下所示:
calculateCube(“hey”).then(r => console.log(r)).catch(e => console.log(e))
现在输出将是:
使用非法参数处理TypeError的示例
你可以观察到用承诺来处理错误是多么容易。此外,您可以将finally()块与promise调用链接起来,以添加将在错误处理完成后运行的代码。
或者,您也可以使用传统的try-catch-finally技术来处理Promise中的错误。在这种情况下,您的承诺电话看起来像这样:
try {let result = await calculateCube(“hey”)console.log(result)} catch (e) {console.log(e)} finally {console.log(‘Finally executed”)}
然而,这仅适用于异步函数。因此,处理Promise中的错误的最好方法是将catch和finally链接到Promise调用。
Throw/catch vs on error () vs回调vs承诺:哪种方法更好?
有四种方法可供您使用,您必须知道如何在任何给定的用例中选择最合适的方法。以下是你可以自己决定的方法:
扔/接住
您将在大多数情况下使用这种方法。确保在catch块中为所有可能的错误实现条件。如果您需要在try块之后运行一些内存清理例程,请记住包括finally块。
然而,太多的try/catch块会使代码难以维护。如果您发现自己处于这种情况,您可能希望通过全局处理程序或promise方法来处理错误。
当在异步try/catch块和promise的catch()之间做出决定时,建议使用异步try/catch块,因为它们将使您的代码具有线性并易于调试。
一个错误()
当您知道您的应用程序必须处理许多错误,并且这些错误可以很好地分布在代码库中时,最好使用onerror()方法。on方法使您能够处理onerror,就像它们只是应用程序处理的另一个事件一样。您可以定义多个错误处理程序,并在初始呈现期间将它们附加到应用程序的窗口。
但是,您还必须记住,在误差范围较小的较小项目中设置onerror()方法可能会带来不必要的挑战。如果您确定您的应用程序不会抛出太多错误,那么传统的throw/catch方法将最适合您。
回访和承诺
回调承诺中的错误处理随着代码设计和结构的不同而不同。但是,如果在编写代码之前在两者之间进行选择,最好使用Promise。
这是因为Promise有一个内置的结构,用于链接catch()和finally()块,以便于处理错误。这种方法比定义额外的参数/重用已有的参数来处理错误更简单明了。
使用Git存储库跟踪变更
由于代码库中的手动错误,经常会出现许多错误。在开发或调试代码时,您最终可能会进行不必要的更改,这可能会导致代码库中出现新的错误。自动化测试是在每次更改后检查代码的好方法。但是,它只能告诉你是否有问题。如果你不经常备份你的代码,你将会浪费时间去修复一个以前运行良好的函数或脚本。
这就是git发挥作用的地方。通过适当的提交策略,您可以将您的git历史作为备份系统来查看您的代码在开发过程中的演变。你可以很容易地浏览旧的提交,发现这个函数的版本之前运行良好,但在不相关的更改后抛出了一个错误。
然后,您可以恢复旧代码或者比较两个版本来确定哪里出错了。现代的Web开发工具(如GitHub Desktop或GitKraken)可以帮助你将这些变化并排可视化,并快速找出错误。
有一个习惯可以帮助您减少错误,那就是当您对代码进行重大更改时,运行代码审查。如果您在团队中工作,您可以创建一个“拉”请求,并让团队成员彻底检查它。这将有助于你用你的第二双眼睛去发现任何你可能遗漏的错误。
处理JavaScript错误的最佳实践
以上方法足以帮助你为下一个JavaScript应用程序设计一个健壮的错误处理方法。但是,在实现过程中最好记住一些事情,以充分利用您的防错功能。这里有一些提示可以帮助你。
1.处理操作异常时使用自定义错误。
我们在本指南前面介绍了自定义错误,让您了解如何根据应用程序的独特情况自定义错误处理。建议尽可能使用自定义错误而不是通用错误类,因为它为调用环境提供了更多有关错误的上下文信息。
最重要的是,自定义错误允许您调整错误在调用环境中的显示方式。这意味着您可以根据需要选择隐藏特定的详细信息或显示有关错误的其他信息。
可以根据需要格式化错误内容。这允许您更好地控制解释和处理错误的方式。
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.捕捉所有未处理的承诺拒绝(Node.js)
类似于您永远无法涵盖所有可能的例外的事实,您很可能会错过处理所有可能的承诺拒绝。然而,与异常不同,承诺拒绝不会引发错误。
因此,一个重要的承诺拒绝可能会作为一个警告溜走,并使您的应用程序暴露于意外行为的可能性。因此,实现回退机制来处理承诺拒绝是非常必要的。
您可以这样做:
const promiseRejectionCallback = error => {console.log(“PROMISE REJECTED: ” + String(error))}process.on(‘unhandledRejection’, callback)
总结
与任何其他编程语言一样,JavaScript中的错误是非常常见和自然的。在某些情况下,您甚至可能需要故意抛出一个错误,以向用户指示正确的响应。因此,了解它们的解剖结构和类型是非常重要的。
此外,您需要配备正确的工具和技术来识别和防止错误导致您的应用程序崩溃。
在大多数情况下,对于所有类型的JavaScript应用程序,通过仔细执行来处理错误的可靠策略就足够了。