JavaScript错误处理指南

分享

啊,JavaScript错误处理的危险。如果你相信墨菲定律任何可能出错的事情,都会出错。在本文中,我将探讨JavaScript中的错误处理。我将介绍一些陷阱、好的实践,最后以异步代码和Ajax结束。

这篇流行的文章于2017年6月8日更新,以解决读者的反馈。具体来说,将文件名添加到代码段中,清理单元测试,并添加包装器模式uglyHandler,增加了关于CORS和第三方错误处理程序的部分。

我觉得JavaScript的事件驱动范式增加了语言的丰富性。我喜欢把浏览器想象成事件驱动的机器,错误也不例外。当发生错误时,将在某个时刻抛出事件。理论上,错误是JavaScript中的简单事件。

如果这对你来说听起来很陌生,系好安全带,因为你将会有一段相当长的旅程。对于本文,我将只关注客户端JavaScript。

本主题基于中解释的概念JavaScript中的异常异常处理.如果你不熟悉的话,我建议你阅读一下基础知识。本文还假定您具有中级水平的JavaScript知识。如果你想要升级,为什么不注册SitePoint Premium并观看我们的课程呢必威西盟体育网页登录JavaScript:下一步.第一课是免费的。

在任何一种情况下,我的目标都是探索处理异常的基本必要条件之外的内容。阅读这篇文章会让你三思而后行,下次你看到一个漂亮的try…catch块。

演示

我们将在本文中使用的演示可以在GitHub,并显示如下页面:

JavaScript演示中的错误处理

所有按钮点击时都会引爆一个“炸弹”。此炸弹模拟抛出的异常TypeError.下面是该模块的定义:

/ /脚本/ error.js函数错误var喷火返回喷火酒吧

首先,这个函数声明一个名为喷火.请注意,酒吧()哪里都找不到定义。让我们用一个好的单元测试来验证这是否会引爆炸弹:

/ /测试/脚本/ errorTest.js'抛出TypeError'函数应该抛出错误TypeError

单元测试已经开始了摩卡使用测试断言Should.js.Mocha是一个测试运行器,而Should.js是断言库。如果您还不熟悉这些测试api,请随意探索。测试开始于(描述)以及格/不及格结束应该.单元测试在Node上运行,不需要浏览器。我建议注意测试,因为它们用简单的JavaScript证明了关键概念。

克隆了repo并安装了依赖项之后,可以使用npm t.或者,你可以像这样运行这个单独的测试:。/ node_modules /摩卡/ bin / /脚本/ errorTest.js摩卡测试

如图所示,错误()定义一个空对象,然后尝试访问一个方法。因为酒吧()对象中不存在,则抛出异常。相信我,对于JavaScript这样的动态语言,每个人都会遇到这种情况!

一些糟糕的错误处理。我从实现中抽象了按钮上的处理程序。下面是处理程序的样子:

/ /脚本/ badHandler.js函数badHandlerfn试一试返回fne返回

此处理程序接收一个fnCallback作为参数。然后在处理函数内部调用此回调。单元测试显示了它的用处:

/ /测试/脚本/ badHandlerTest.js“返回无错误的值”函数varfn函数返回1var结果badHandlerfn结果应该平等的1“返回错误的空值”函数varfn函数错误随机误差的var结果badHandlerfn应该结果平等的

如您所见,这个糟糕的错误处理程序返回如果出了问题。回调fn ()可以指向合法的方法或者炸弹。

下面的点击事件处理程序告诉了剩下的故事:

/ /脚本/ badHandlerDom.js函数处理程序炸弹varbadButton文档getElementById“坏”如果badButtonbadButtonaddEventListener“点击”函数处理程序炸弹控制台日志“想象一下,因为隐瞒错误而获得晋升。”badHandler错误

糟糕的是我只有一个.当我试图找出哪里出了问题时,这让我变得盲目。这种无故障策略的范围从糟糕的用户体验一直到数据损坏。令人沮丧的是,我可以花费数小时调试该症状,但却错过了try-catch块。这个邪恶的处理程序在代码中吞下错误,并假装一切正常。对于不注重代码质量的组织来说,这可能是可以接受的。但是,隐藏错误会让你在未来花费数小时进行调试。在具有深度调用堆栈的多层解决方案中,不可能找出哪里出了问题。至于错误处理,这是非常糟糕的。

无声失败策略会让你渴望更好的错误处理。JavaScript提供了一种更优雅的处理异常的方式。

丑陋的

是时候调查一个丑陋的处理程序了。我将跳过与DOM紧密耦合的部分。这里与你看到的糟糕的处理程序没有区别。

/ /脚本/ uglyHandler.js函数uglyHandlerfn试一试返回fne错误'一个新的错误'

重要的是它处理异常的方式,如下面的单元测试所示:

/ /测试/脚本/ uglyHandlerTest.js'返回一个带有错误的新错误'函数varfn函数TypeError类型错误的应该抛出函数uglyHandlerfn错误

比起糟糕的教练,这是一个明显的进步。在这里,异常通过调用堆栈被冒泡。我现在喜欢的是错误会Unwind堆栈这对调试非常有帮助。除了一个异常,解释器会沿着堆栈向上移动,寻找另一个处理程序。这为处理调用堆栈顶部的错误提供了许多机会。不幸的是,由于它是一个丑陋的处理程序,我失去了原始的错误。因此,我被迫遍历堆栈以找出原始异常。至少我知道出了什么问题,这就是为什么抛出异常的原因。

作为一种替代方法,可以使用自定义错误结束丑陋的处理程序。当你在错误中添加更多细节时,它不再是丑陋的,而是有用的。关键是要附加关于错误的特定信息。

例如:

/ /脚本/ specifiedError.js//创建自定义错误varSpecifiedError函数SpecifiedError消息的名字“SpecifiedError”消息消息||堆栈错误堆栈SpecifiedError原型错误SpecifiedError原型构造函数SpecifiedError
/ /脚本/ uglyHandlerImproved.js函数uglyHandlerImprovedfn试一试返回fneSpecifiedErrore消息
/ /测试/脚本/ uglyHandlerImprovedTest.js'返回带有错误的指定错误'函数varfn函数TypeError类型错误的应该抛出函数uglyHandlerImprovedfnSpecifiedError

指定的错误将添加更多详细信息并保留原始错误消息。有了这个改进,它不再是一个丑陋的处理程序,而是干净和有用的。

使用这些处理程序,我仍然得到一个未处理的异常。让我们看看浏览器是否有办法解决这个问题。

Unwind堆栈

unwind异常的一种方法是放置try…catch在调用堆栈的顶部。

例如:

函数主要炸弹试一试炸弹e//处理所有错误

但是,记得我说过浏览器是事件驱动的吗?是的,JavaScript中的异常只不过是一个事件。解释器在执行上下文中停止执行并展开。事实证明,有一个Onerror全局事件处理程序我们可以用。

大概是这样的:

/ /脚本/ errorHandlerDom.js窗口addEventListener“错误”函数evar错误e错误控制台日志错误

此事件处理程序在任何执行上下文中捕获错误。任何类型的错误都会从不同的目标触发错误事件。如此激进的是这个事件处理程序将错误处理集中在代码中。与任何其他事件一样,您可以使用菊花链处理程序来处理特定的错误。这使得错误处理程序只有一个目的固体的原则。这些处理程序可以在任何时候注册。解释器将循环遍历所需的所有处理程序。释放代码库try…catch块遍布,这使得调试很容易。关键是像对待JavaScript中的事件处理一样对待错误处理。

现在有了一种使用全局处理程序展开堆栈的方法,我们可以用它做什么呢?

毕竟,愿调用堆栈与您同在。

捕获堆栈

调用堆栈在故障排除方面非常有用。好消息是浏览器提供了开箱即用的信息。的堆栈财产不是标准的一部分,但在最新的浏览器上始终可用。

所以,例如,你现在可以在服务器上记录错误:

/ /脚本/ errorAjaxHandlerDom.js窗口addEventListener“错误”函数evar堆栈e错误堆栈var消息e错误toString如果堆栈消息+ =' \ n '+堆栈varxhrXMLHttpRequestxhr开放“职位”“/日志”真正的//触发一个带有错误细节的Ajax请求xhr发送消息

从这个例子中可能不太明显,但这将与前面的例子一起启动。每个错误处理程序都有一个保存代码的目的

在浏览器中,事件处理程序获取附加到DOM。这意味着如果您正在构建第三方库,您的事件将与客户端代码共存。的window.addEventListener ()为您解决这个问题,它不会掩盖现有的事件。

下面是这个日志在服务器上的截图:

Ajax日志请求到节点服务器

这个日志存在于命令提示符中,是的,它毫无疑问地运行在Windows上。

此消息来自Firefox Developer Edition 54。使用适当的错误处理程序,请注意问题是什么是非常清楚的。不需要隐藏错误,通过瞥一眼这个,我可以看到是什么抛出异常以及在哪里抛出异常。这种级别的透明性有利于调试前端代码。您可以分析日志,了解哪些条件触发了哪些错误。

调用栈对调试很有帮助,千万不要低估调用栈的作用。

一个问题是,如果你有一个来自不同领域的脚本使歌珥您不会看到任何错误细节。例如,当您在CDN上放置脚本以利用每个域六个请求的限制时,就会发生这种情况。的e.message只会说“脚本错误”,这是不好的。在JavaScript中,错误信息仅对单个域可用。

一种解决方案是重新抛出错误,同时保留错误消息:

试一试返回fne错误e消息

一旦重新抛出错误,全局错误处理程序将完成剩下的工作。只需确保错误处理程序位于相同的域中。您甚至可以用特定的错误信息将其包装在自定义错误中。这将保留原始消息、堆栈和自定义错误对象。

异步处理

啊,异步的危险。JavaScript将异步代码从执行上下文中分离出来。这意味着像下面这样的异常处理程序有一个问题:

/ /脚本/ asyncHandler.js函数asyncHandlerfn试一试//从当前上下文中删除潜在的炸弹setTimeout函数fn1e

单元测试会告诉你剩下的事情:

/ /测试/脚本/ asyncHandlerTest.js“不捕获带有错误的异常”函数//炸弹varfn函数TypeError类型错误的//检查异常是否被捕获应该doesNotThrow函数asyncHandlerfn

异常没有被捕获,我可以用这个单元测试来验证这一点。注意,发生了一个未处理的异常,尽管我将代码包装在nicetry…catch.是的,try…catch语句只能在单个执行上下文中工作。异常抛出时,解释器已经从try…catch.同样的行为也发生在Ajax调用中。

因此,一种替代方法是在异步回调中捕获异常:

setTimeout函数试一试fne//处理这个异步错误1

这种方法是可行的,但仍有很大的改进空间。首先,try…catch街区到处都被缠在一起。事实上,20世纪70年代的糟糕编程打来电话,他们想要回他们的代码。另外,V8引擎不鼓励使用试着在函数中捕获块.V8是Chrome浏览器和Node中使用的JavaScript引擎。一种想法是将块移动到调用堆栈的顶部,但这对异步代码不起作用。

那么,这将把我们引向何方?我说全局错误处理程序在任何执行上下文中操作是有原因的。如果您向窗口对象添加错误处理程序,那么就完成了!保持干燥和坚固的决定正在得到回报,这很好。全局错误处理程序将使您的异步代码整洁。

下面是这个异常处理程序在服务器上报告的内容。注意,如果按照下面的步骤进行操作,所看到的输出将根据所使用的浏览器而有所不同。

服务器上的异步错误报告

这个处理程序甚至告诉我错误来自异步代码。它说它来自于setTimeout ()函数。太酷了!

结论

在错误处理领域,至少有两种方法。一种是故障沉默方法,即忽略代码中的错误。另一种是快速失败和放松的方法,在这种方法中,错误会让世界停止并倒带。我认为我支持哪一个,以及为什么支持,这是很清楚的。我的观点是:不要隐藏问题。没有人会因为程序中可能发生的意外而羞辱你。停止,倒带,再给用户一次尝试是可以接受的。

在一个远非完美的世界里,允许第二次机会是很重要的。错误是不可避免的,重要的是你怎么做。

本文由蒂姆Severien而且莫里茨克罗格.感谢所有SitePoint的同行审必威西盟体育网页登录稿人,让SitePoint的内容成为最好的!

Baidu