墨菲定律指出,任何可能出错的事情最终都会出错。这在编程世界中应用得太好了。如果您创建一个应用程序,您很可能会产生错误和其他问题。JavaScript中的错误就是这样一个常见问题!
犯错是人之常情。这就是为什么我们将向您展示您需要了解的有关处理JavaScript错误的所有信息。
本文将引导您了解JavaScript中的基本错误,并解释您可能遇到的各种错误。然后,您将学习如何识别和修复这些错误。还有一些技巧可以在生产环境中有效地处理错误。
编程错误是指程序无法正常运行的情况。当程序不知道如何处理手头的工作时,可能会发生这种情况,例如尝试打开不存在的文件或在没有网络连接的情况下访问基于Web的API端点时。
这些情况促使程序向用户抛出错误,说明它不知道如何继续。该程序收集尽可能多的有关错误的信息,然后报告它无法继续前进。
JavaScript中的错误是在发生编程错误时显示的对象。这些对象包含有关错误类型、导致错误的语句以及发生错误时的堆栈跟踪的大量信息。JavaScript还允许程序员创建自定义错误,以便在调试问题时提供额外信息。
现在JavaScript错误的定义已经很清楚了,是时候深入研究细节了。
JavaScript中的错误带有某些标准和自定义属性,有助于理解错误的原因和影响。默认情况下,JavaScript中的错误包含三个属性:
此外,error还可以携带columnNumber、lineNumber、fileName等属性,以更好地描述错误。但是,这些属性不是标准的,可能会出现在JavaScript应用程序生成的每个错误对象中,也可能不会出现。
堆栈跟踪是发生异常或警告等事件时程序所在的方法调用列表。这是伴随异常的示例堆栈跟踪的样子:
堆栈跟踪示例
如您所见,它首先打印错误名称和消息,然后是被调用的方法列表。每个方法调用都说明其源代码的位置以及调用它的行。您可以使用这些数据浏览您的代码库并确定是哪段代码导致了错误。
此方法列表以堆叠方式排列。它显示了您的异常首次引发的位置以及它如何通过堆叠的方法调用传播。为异常实现捕获不会让它通过堆栈向上传播并使您的程序崩溃。但是,您可能希望在某些情况下故意不捕获致命错误以使程序崩溃。
大多数人通常将错误和异常视为同一件事。但是,必须注意它们之间的细微但根本的区别。
异常是已抛出的错误对象。
为了更好地理解这一点,让我们举一个简单的例子。以下是如何在JavaScript中定义错误:
constwrongTypeError=TypeError("Wrongtypefound,expectedcharacter")这就是wrongTypeError对象变成异常的方式:
throwwrongTypeError然而,大多数人倾向于使用在抛出错误对象时定义错误对象的简写形式:
throwTypeError("Wrongtypefound,expectedcharacter")这是标准做法。然而,这也是开发人员倾向于混淆异常和错误的原因之一。因此,即使您使用速记来快速完成工作,了解基础知识也至关重要。
JavaScript中有一系列预定义的错误类型。只要程序员没有明确处理应用程序中的错误,它们就会由JavaScript运行时自动选择和定义。
当变量设置为超出其合法值范围时,会引发RangeError。它通常发生在将值作为参数传递给函数时,并且给定值不在函数参数的范围内。使用文档记录不佳的第三方库时,有时修复起来会很棘手,因为您需要知道参数的可能值范围才能传递正确的值。
RangeError发生的一些常见场景是:
当您的代码中的变量引用出现问题时,就会发生ReferenceError。您可能忘记在使用变量之前为其定义值,或者您可能试图在代码中使用不可访问的变量。在任何情况下,通过堆栈跟踪提供了充足的信息来查找和修复有错误的变量引用。
ReferenceErrors发生的一些常见原因是:
可能发生SyntaxErrors的一些常见原因是:
最好在IDE中使用linting工具在此类错误出现在浏览器之前为您识别这些错误。
TypeError是JavaScript应用程序中最常见的错误之一。当某些值不是特定的预期类型时,会创建此错误。发生时的一些常见情况是:
发生TypeError的可能性还有很多。稍后我们将查看一些著名的实例并学习如何修复它们。
当JavaScript运行时引擎中发生异常时使用InternalError类型。它可能表示也可能不表示您的代码存在问题。
通常,InternalError仅在两种情况下发生:
解决此错误的最合适方法是通过错误消息确定原因,并在可能的情况下重构您的应用程序逻辑,以消除JavaScript引擎上的工作负载突然激增。
诊断这些错误通常很容易,因为您只需要检查参数是否存在畸形。
当函数eval()调用发生错误时会发生EvalError。eval()函数用于执行存储在字符串中的JavaScript代码。但是,由于安全问题,强烈建议不要使用eval()函数,并且当前的ECMAScript规范不再抛出EvalError类,因此存在此错误类型只是为了保持与旧版JavaScript代码的向后兼容性。
如果您使用的是旧版本的JavaScript,则可能会遇到此错误。无论如何,最好调查eval()函数调用中执行的代码是否有任何异常。
虽然JavaScript提供了足够的错误类型列表来涵盖大多数场景,但如果列表不满足您的要求,您始终可以创建新的错误类型。这种灵活性的基础在于JavaScript允许您使用throw命令逐字地抛出任何东西。
throw8throw"Anerroroccurred"但是,抛出原始数据类型不会提供有关错误的详细信息,例如其类型、名称或随附的堆栈跟踪。为了解决这个问题并标准化错误处理过程,我们提供了Error这个类。也不鼓励在抛出异常时使用原始数据类型。
您可以扩展Error类以创建您的自定义错误类。以下是如何执行此操作的基本示例:
classValidationErrorextendsError{constructor(message){super(message);this.name="ValidationError";}}您可以通过以下方式使用它:
throwValidationError("Propertynotfound:name")然后您可以使用instanceof关键字识别它:
try{validateForm()//codethatthrowsaValidationError}catch(e){if(einstanceofValidationError)//dosomethingelse//dosomethingelse}JavaScript中最常见的10个错误现在您已经了解了常见的错误类型以及如何创建自定义错误类型,是时候看看您在编写JavaScript代码时会遇到的一些最常见的错误了。
在几种不同的情况下,GoogleChrome中会出现此错误。首先,如果您调用递归函数并且它不会终止,则可能会发生这种情况。您可以在Chrome开发者控制台中自行查看:
带有递归函数调用的RangeError示例
因此,要解决此类错误,请确保正确定义递归函数的边界情况。发生此错误的另一个原因是您传递的值超出了函数的参数范围。这是一个例子:
带有toExponential()调用的RangeError示例
错误消息通常会指出您的代码有什么问题。一旦你做出改变,它就会得到解决。
toExponential()函数调用的输出
当您在未定义的引用上设置属性时会发生此错误。您可以使用此代码重现该问题:
varlistlist.count=0这是您将收到的输出:
类型错误示例
要修复此错误,请在访问其属性之前使用值初始化引用。以下是修复后的外观:
如何修复类型错误
这是JavaScript中最常出现的错误之一。当您尝试读取属性或调用未定义对象的函数时,会发生此错误。您可以通过在Chrome开发人员控制台中运行以下代码来非常轻松地重现它:
varfuncfunc.call()这是输出:
带有未定义函数的TypeError示例
未定义的对象是导致此错误的众多可能原因之一。此问题的另一个突出原因可能是在呈现UI时未正确初始化状态。这是来自React应用程序的真实示例:
importReact,{useState,useEffect}from"react";constCardsList=()=>{const[state,setState]=useState();useEffect(()=>{setTimeout(()=>setState({items:["Card1","Card2"]}),2000);},[]);return(<>{state.items.map((item)=>(
浏览器中的TypeError堆栈跟踪
这是因为,在渲染时,状态容器是未定义的;因此,它不存在任何财产items。修复这个错误很容易。您只需要为状态容器提供初始默认值。
//...const[state,setState]=useState({items:[]});//...现在,在设置延迟之后,您的应用程序将显示类似的输出:
代码输出
代码中的确切修复可能会有所不同,但这里的本质是始终在使用变量之前正确初始化它们。
当您尝试访问未定义对象的属性或调用未定义对象的方法时,会在Safari中发生此错误。您可以从上面运行相同的代码来自己重现错误。
这个错误的解决方法也是一样的——确保你已经正确地初始化了你的变量,并且在访问一个属性或方法时它们不是未定义的。
这又与前面的错误相似。它发生在Safari上,这两个错误之间的唯一区别是,当正在访问其属性或方法的对象null不是undefined.您可以通过运行以下代码来重现这一点:
varfunc=nullfunc.call()这是您将收到的输出:
带有null函数的TypeError示例
因为null是显式设置为变量的值,而不是由JavaScript自动分配的值。仅当您尝试访问null自己设置的变量时,才会发生此错误。因此,您需要重新访问您的代码并检查您编写的逻辑是否正确。
当您尝试读取null或undefined对象的长度时,Chrome中会出现此错误。这个问题的原因和前面的问题类似,但是在处理列表的时候出现的频率比较高;因此值得特别提及。以下是重现问题的方法:
带有未定义对象的TypeError示例
但是,在较新版本的Chrome中,此错误报告为UncaughtTypeError:Cannotreadpropertiesofundefined.这是它现在的样子:
在较新的Chrome版本上带有未定义对象的TypeError示例
再次,修复是确保您尝试访问其长度的对象存在并且未设置为null.
当您尝试调用脚本中不存在的方法或该方法存在但无法在调用上下文中引用时,会发生此错误。这个错误通常发生在谷歌浏览器中,您可以通过检查抛出错误的代码行来解决它。如果您发现拼写错误,请修复它并检查它是否能解决您的问题。
如果您在代码中使用了自引用关键字this,如果this没有适当地绑定到您的上下文,则可能会出现此错误。考虑下面的代码:
functionshowAlert(){alert("messagehere")}document.addEventListener("click",()=>{this.showAlert();})如果执行上述代码,它将抛出我们讨论过的错误。之所以会发生这种情况,是因为作为事件侦听器传递的匿名函数正在document的上下文中执行。
相比之下,showAlert函数是在window的上下文中定义的。
为了解决这个问题,您必须通过将函数与bind()方法绑定来传递对函数的正确引用:
document.addEventListener("click",this.showAlert.bind(this))8.ReferenceError:eventisnotdefined当您尝试访问未在调用范围内定义的引用时,会发生此错误。这通常发生在处理事件时,因为它们经常为您提供event回调函数中调用的引用。如果您忘记在函数的参数中定义事件参数或拼写错误,则可能会发生此错误。
在InternetExplorer或GoogleChrome中可能不会发生此错误(因为IE提供了一个全局事件变量,并且Chrome会自动将事件变量附加到处理程序),但它可能在Firefox中发生。所以建议留意这样的小错误。
这是由于粗心造成的错误。如果您尝试将新值分配给常量变量,您将遇到这样的结果:
带有常量对象分配的TypeError示例
当第三方脚本向您的浏览器发送错误时,就会发生脚本错误。此错误后跟(未知),因为第三方脚本与您的应用属于不同的域。浏览器隐藏了其他细节,以防止第三方脚本泄露敏感信息。
在不了解完整详细信息的情况下,您无法解决此错误。您可以执行以下操作来获取有关该错误的更多信息:
一旦您可以访问错误的详细信息,您就可以着手解决问题,这可能与第三方库或网络有关。
虽然上面讨论的错误是JavaScript中最常见和最常见的错误,但您会遇到,仅仅依靠几个示例是远远不够的。在开发JavaScript应用程序时,了解如何检测和防止任何类型的错误至关重要。以下是如何处理JavaScript中的错误。
处理手动或运行时抛出的错误的最基本方法是捕获它们。与大多数其他语言一样,JavaScript提供了一组关键字来处理错误。在着手处理JavaScript应用程序中的错误之前,必须深入了解它们中的每一个。
throw
该集合的第一个也是最基本的关键字是throw.很明显,throw关键字用于抛出错误以在JavaScript运行时手动创建异常。我们已经在本文前面讨论过这个问题,这里是这个关键字意义的要点:
try
try关键字用于指示代码块可能会引发异常。它的语法是:
try{//error-pronecodehere}重要的是要注意,catch块必须始终跟随try块才能有效地处理错误。
catch
catch关键字用于创建一个catch块。此代码块负责处理尾随try块捕获的错误。这是它的语法:
catch(exception){//codetohandletheexceptionhere}这就是你如何一起实现try和catch块的方式:
try{//businesslogiccode}catch(exception){//errorhandlingcode}与C++或Java不同,您不能将多个catch块附加到JavaScript中的try块。这意味着您不能这样做:
try{//businesslogiccode}catch(exception){if(exceptioninstanceofTypeError){//dosomething}}catch(exception){if(exceptioninstanceofRangeError){//dosomething}}相反,您可以在单个catch块中使用if...else语句或switchcase语句来处理所有可能的错误情况。它看起来像这样:
try{//businesslogiccode}catch(exception){if(exceptioninstanceofTypeError){//dosomething}elseif(exceptioninstanceofRangeError){//dosomethingelse}}finally
finally关键字用于定义在处理错误后运行的代码块。该块在try和catch块之后执行。
此外,无论其他两个块的结果如何,都会执行finally块。这意味着即使catch块不能完全处理错误或者catch块中抛出错误,解释器也会在程序崩溃之前执行finally块中的代码。
要被认为是有效的,JavaScript中的try块需要后跟catch或finally块。如果没有这些,解释器将引发SyntaxError。因此,在处理错误时,请确保至少遵循您的try块。
onerror()方法适用于所有HTML元素,用于处理它们可能发生的任何错误。例如,如果img标签找不到指定URL的图像,它会触发其onerror方法以允许用户处理错误。
通常,您会在onerror调用中提供另一个图像URL,以便img标记回退到。这是您可以通过JavaScript执行此操作的方法:
constimage=document.querySelector("img")image.onerror=(event)=>{console.log("Erroroccurred:"+event)}但是,您可以使用此功能为您的应用创建全局错误处理机制。以下是您的操作方法:
window.onerror=(event)=>{console.log("Erroroccurred:"+event)}使用此事件处理程序,您可以摆脱try...catch代码中的多个块,并集中您的应用程序的错误处理,类似于事件处理。您可以将多个错误处理程序附加到窗口,以维护SOLID设计原则中的单一责任原则。解释器将循环遍历所有处理程序,直到到达适当的处理程序。
虽然简单和线性函数允许错误处理保持简单,但回调会使事情复杂化。
考虑以下代码:
如果您尝试在函数调用中输入字符串而不是4,您将得到NaN结果。
这需要妥善处理。就是这样:
constcalculateCube=(number,callback)=>{setTimeout(()=>{if(typeofnumber!=="number")thrownewError("Numericargumentisexpected")constcube=number*number*numbercallback(cube)},1000)}constcallback=result=>console.log(result)try{calculateCube(4,callback)}catch(e){console.log(e)}这应该可以理想地解决问题。但是,如果您尝试将字符串传递给函数调用,您将收到以下信息:
错误参数的错误示例
即使您在调用函数时实现了try-catch块,它仍然表示错误未捕获。由于超时延迟,在执行catch块后抛出错误。
这可能在网络调用中很快发生,在这种情况下会出现意外延迟。您需要在开发应用程序时涵盖此类情况。
以下是在回调中正确处理错误的方法:
constcalculateCube=(number,callback)=>{setTimeout(()=>{if(typeofnumber!=="number"){callback(newTypeError("Numericargumentisexpected"))return}constcube=number*number*numbercallback(null,cube)},2000)}constcallback=(error,result)=>{if(error!==null){console.log(error)return}console.log(result)}try{calculateCube('hey',callback)}catch(e){console.log(e)}现在,控制台的输出将是:
带有非法参数的TypeError示例
这表明错误已得到适当处理。
大多数人倾向于使用Promise来处理异步活动。Promise还有另一个优点——被拒绝的Promise不会终止你的脚本。但是,您仍然需要实现一个catch块来处理Promise中的错误。为了更好地理解这一点,让我们使用Promises重写calculateCube()函数:
constdelay=ms=>newPromise(res=>setTimeout(res,ms));constcalculateCube=async(number)=>{if(typeofnumber!=="number")throwError("Numericargumentisexpected")awaitdelay(5000)constcube=number*number*numberreturncube}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{letresult=awaitcalculateCube("hey")console.log(result)}catch(e){console.log(e)}finally{console.log('Finallyexecuted")}但是,这仅适用于异步函数。因此,在Promise中处理错误的最优选方式是链式连接catch和finally连接到Promise调用。
有四种方法可供您使用,您必须知道如何在任何给定的用例中选择最合适的方法。以下是您可以自己决定的方法:
throw/catch
您将在大多数情况下使用此方法。确保在你的catch块中为所有可能的错误实现条件,如果你需要在try块之后运行一些内存清理例程,请记住包含一个finally块。
但是,太多的try/catch块会使您的代码难以维护。如果您发现自己处于这种情况,您可能希望通过全局处理程序或promise方法来处理错误。
在异步try/catch块和promise的catch()之间做出决定时,建议使用异步try/catch块,因为它们将使您的代码线性且易于调试。
onerror()
当您知道您的应用程序必须处理许多错误并且它们可以很好地分散在整个代码库中时,最好使用onerror()方法。onerror方法使您能够处理错误,就好像它们只是您的应用程序处理的另一个事件一样。您可以定义多个错误处理程序并在初始呈现时将它们附加到应用程序的窗口。
但是,您还必须记住,在错误范围较小的较小项目中设置onerror()方法可能会带来不必要的挑战。如果您确定您的应用程序不会抛出太多错误,那么传统的throw/catch方法将最适合您。
CallbacksandPromises
回调和承诺中的错误处理因代码设计和结构而异。但是,如果您在编写代码之前在这两者之间进行选择,最好使用Promise。
这是因为Promise具有用于链接catch()和finally()块以轻松处理错误的内置结构。这种方法比定义附加参数/重用现有参数来处理错误更容易和更清晰。
一个可以帮助您减少错误的习惯是在您对代码进行重大更改时运行代码审查。如果您在一个团队中工作,您可以创建一个拉取请求并让团队成员彻底审查它。这将帮助您使用第二双眼睛来发现您可能遗漏的任何错误。
上述方法足以帮助您为下一个JavaScript应用程序设计一个健壮的错误处理方法。但是,最好在实施时记住一些事情,以充分利用您的防错功能。这里有一些提示可以帮助您。
我们在本指南的前面介绍了自定义错误,让您了解如何根据应用程序的独特情况自定义错误处理。建议尽可能使用自定义错误而不是泛型Error类,因为它为调用环境提供了有关错误的更多上下文信息。
最重要的是,自定义错误允许您调整错误在调用环境中的显示方式。这意味着您可以根据需要选择隐藏特定详细信息或显示有关错误的其他信息。
您可以根据需要格式化错误内容。这使您可以更好地控制错误的解释和处理方式。
即使是最资深的开发人员也经常犯一个新手错误——在他们的代码中使用异常级别。
您可能会遇到有一段代码可以选择运行的情况。如果它有效,那就太好了;如果没有,你不需要做任何事情。
在这些情况下,通常很想将这段代码放在try块中,并在其上附加一个空的catch块。但是,通过这样做,您将使那段代码保持开放状态,从而导致任何类型的错误并逃脱惩罚。如果你有一个庞大的代码库和许多这样糟糕的错误管理结构的实例,这可能会变得很危险。
处理异常的最好方法是确定所有异常都将被处理的级别并将它们提高到那里。此级别可以是控制器(在MVC架构应用程序中)或中间件(在传统的面向服务器的应用程序中)。
通过这种方式,您将了解在哪里可以找到应用程序中发生的所有错误并选择如何解决它们,即使这意味着不对它们做任何事情。
记录错误通常是处理错误的一个组成部分。那些未能制定集中策略来记录错误的人可能会错过有关其应用程序使用情况的宝贵信息。
应用程序的事件日志可以帮助您找出有关错误的关键数据并帮助快速调试它们。如果您在应用程序中设置了适当的警报机制,您可以在错误到达大部分用户群之前知道应用程序何时发生错误。
建议使用预先构建的记录器或创建一个以满足您的需求。您可以配置此记录器以根据其级别(警告、调试、信息等)处理错误,并且一些记录器甚至可以立即将日志发送到远程记录服务器。通过这种方式,您可以观察应用程序的逻辑在活动用户中的执行情况。
在定义错误处理策略时要牢记的另一个要点是牢记用户。
如果错误不会对日常用户体验造成任何干扰,您可以考虑抑制警报并将错误记录到远程服务器以供以后解决。
Node.js环境支持中间件向服务器应用程序添加功能。您可以使用此功能为您的服务器创建错误处理中间件。
使用中间件的最大好处是所有错误都集中在一个地方处理。您可以轻松选择启用/禁用此设置以进行测试。
以下是创建基本中间件的方法:
constlogError=err=>{console.log("ERROR:"+String(err))}consterrorLoggerMiddleware=(err,req,res,next)=>{logError(err)next(err)}constreturnErrorMiddleware=(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)您现在可以在中间件中定义自定义逻辑以适当地处理错误。您不再需要担心在整个代码库中实现单个错误处理结构。
当Node.js应用程序遇到程序员错误时,它们可能不一定会抛出异常并尝试关闭应用程序。此类错误可能包括由程序员错误引起的问题,例如高CPU消耗、内存膨胀或内存泄漏。处理这些问题的最佳方法是通过Node.js集群模式或PM2等独特工具使应用程序崩溃,从而优雅地重新启动应用程序。这可以确保应用程序不会因用户操作而崩溃,从而呈现糟糕的用户体验。
您永远无法确定您已经涵盖了您的应用程序中可能出现的所有错误。因此,实施备用策略以从您的应用程序中捕获所有未捕获的异常非常重要。
您可以这样做:
process.on('uncaughtException',error=>{console.log("ERROR:"+String(error))//otherhandlingmechanisms})您还可以确定发生的错误是标准异常还是自定义操作错误。根据结果,您可以退出进程并重新启动它以避免意外行为。
与您永远无法涵盖所有可能的异常类似,您很有可能会错过处理所有可能的PromiseRejections。但是,与异常不同,PromiseRejection不会引发错误。
因此,一个重要的PromiseRejection可能会作为警告而溜走,并使您的应用程序面临遇到意外行为的可能性。因此,实现一个回退机制来处理PromiseRejection是至关重要的。
constpromiseRejectionCallback=error=>{console.log("PROMISEREJECTED:"+String(error))}process.on('unhandledRejection',callback)小结与任何其他编程语言一样,JavaScript中的错误非常频繁且自然。在某些情况下,您甚至可能需要故意抛出错误以向用户指示正确的响应。因此,了解它们的解剖结构和类型非常重要。
此外,您需要配备正确的工具和技术来识别和防止错误导致您的应用程序崩溃。
在大多数情况下,对于所有类型的JavaScript应用程序来说,通过仔细执行来处理错误的可靠策略就足够了。