JavaScript的事件驱动范式为语言增添了丰富性,也让JavaScript编程更加多样化。如果您将浏览器视为JavaScript的事件驱动工具,那么当发生错误时,将抛出一个事件。理论上,这些错误可以被认为是JavaScript中的简单事件。本文将讨论客户端JavaScript中的错误处理。主要介绍JavaScript中易犯错误、错误处理、异步代码编写等。让我们来看看如何正确处理JavaScript中的错误。Demo演示本文中使用的demo可以在GitHub上找到。运行后会是这个页面:每个按钮都会抛出一个“错误(Exception)”,这个错误会模拟一个抛出的异常TypeError。这是模块定义://scripts/error.jsfunctionerror(){varfoo={};returnfoo.bar();}首先,这个函数声明了一个空对象foo。注意bar()没有在任何地方定义。接下来验证此单元测试是否抛出“错误”://tests/scripts/errorTest.jsit('throwsaTypeError',function(){should.throws(error,TypeError);});这个单元测试在Mocha中,同时在Should.js中有测试声明。Mocha是测试运行器,Should.js是断言库。此单元测试在Node上运行,不需要浏览器。error()定义了一个空对象,然后尝试访问一个方法。因为对象中不存在bar(),所以抛出异常。这种错误,发生在像JavaScript这样的动态语言身上,大概每个人都会发生!错误处理(1)通过以下代码处理上述错误://scripts/badHandler.jsfunctionbadHandler(fn){try{returnfn();}catch(e){}returnnull;}这个handler将fn作为输入参数,然后fn会在handler函数内部被调用。单元测试将反映上述错误处理程序的效果://tests/scripts/badHandlerTest.jsit('returnsavaluewithouterrors',function(){varfn=function(){return1;};varresult=badHandler(fn);result.should.equal(1);});it('返回一个有错误的null',function(){varfn=function(){thrownewError('randomerror');};varresult=badHandler(fn);should(result).equal(null);});如果出现问题,错误处理程序将返回null。fn()回调函数可以指向一个合法的方法或一个错误。下面的点击事件会继续被处理://scripts/badHandlerDom.js(function(handler,bomb){varbadButton=document.getElementById('bad');if(badButton){badButton.addEventListener('click',function(){handler(bomb);console.log('想象一下,因为隐藏错误而得到提升');});}}(badHandler,error));这种处理隐藏了代码中的错误并且很难找到。隐藏的错误可能会花费数小时的调试时间。尤其是在调用栈很深的多层解决方案中,这个bug会更难发现。所以这是一种糟糕的错误处理方式。错误处理(二)下面是另一种错误处理方法。//scripts/uglyHandler.jsfunctionuglyHandler(fn){try{returnfn();}catch(e){thrownewError('一个新错误');}}以下是异常的处理方式://tests/scripts/uglyHandlerTest.jsit('returnsanewerrorwitherrors',function(){varfn=function(){thrownewTypeError('typeerror');};should.throws(function(){uglyHandler(fn);},错误);});上面对错误处理过程有了很大的改进。这里的异常在调用堆栈中冒泡。错误也会展开堆栈,这对调试非常有帮助。除了抛出异常之外,解释器还会沿着堆栈寻找其他处理。这也开启了从堆栈顶部处理错误的可能性。但这仍然是糟糕的错误处理,需要我们从栈中退回到原来的异常。结束这种糟糕的错误处理的另一种方法是自定义错误处理。当您向错误添加更多详细信息时,此方法会很有帮助。例如://scripts/specifiedError.js//创建自定义错误varSpecifiedError=functionSpecifiedError(message){this.name='SpecifiedError';this.message=消息||'';this.stack=(newError()).stack;};SpecifiedError.prototype=newError();SpecifiedError.prototype.constructor=SpecifiedError;//scripts/uglyHandlerImproved.jsfunctionuglyHandlerImproved(fn){尝试{returnfn();}catch(e){thrownewSpecifiedError(e.message);}}//tests/scripts/uglyHandlerImprovedTest.jsit('返回指定的错误错误',function(){varfn=function(){thrownewTypeError('typeerror');};should.throws(function(){uglyHandlerImproved(fn);},SpecifiedError);});指定的错误会添加更多详细信息并保留原始错误消息。有了这个改进,上面的处理方式不再是一种糟糕的做事方式,而是一种清晰有效的方式。经过上面的处理,我们也收到了未处理的异常。接下来让我们看看浏览器在处理错误时可以做什么来提供帮助。展开堆栈以处理异常的一种方法是在调用堆栈的顶部添加一个try...catch。例如:functionmain(bomb){try{bomb();}catch(e){//处理所有错误的事情}}但是,浏览器是事件驱动的,JavaScript中的异常也是事件。当异常发生时,解释器暂停执行并展开://scripts/errorHandlerDom.jswindow.addEventListener('error',function(e){varerror=e.error;console.log(error);});此事件处理程序捕获在任何执行上下文中发生的错误。各种目标上的错误事件会触发各种类型的错误。代码中的这种集中式错误处理非常激进。您可以使用菊花链来处理特定错误。如果遵循SOLID原则,则可以采用单一用途的错误处理方法。这些处理程序可以随时注册,解释器会循环遍历需要执行的处理程序。代码库可以从try...catch块中解放出来,这也使调试变得容易。在JavaScript中,将错误处理视为事件处理很重要。捕获堆栈调用堆栈在解决问题时非常有用,浏览器只提供该信息。尽管stack属性不是标准的一部分,但现代浏览器已经可以查看此信息。这是在服务器上记录错误的示例://scripts/errorAjaxHandlerDom.jswindow.addEventListener('error',function(e){varstack=e.error.stack;varmessage=e.error.toString();if(stack){message+='\n'+stack;}varxhr=newXMLHttpRequest();xhr.open('POST','/log',true);//触发带有错误详细信息的Ajax请求xhr。发信息);});每个错误处理都有一个单一的目的,保持了代码的DRY原则(单一目的,不要重复自己原则)。在浏览器中,事件处理需要添加到DOM中。这意味着如果您正在构建第三方库,您的事件将与客户端代码共存。window.addEventListener()将在不清除现有事件的情况下为您处理它。下面是服务器上日志的截图:日志可以通过命令提示符查看,但是在Windows上,日志不是动态的。通过日志可以清楚的看到具体情况触发了什么错误。调用堆栈在调试时也非常有用,所以不要低估调用堆栈的力量。在JavaScript中,错误消息仅适用于单个域。因为当使用来自不同域的脚本时,您不会看到任何错误详细信息。一种解决方案是在保留错误消息的同时重新抛出错误:try{returnfn();}catch(e){thrownewError(e.message);}其余的部分。确保您的错误处理在同一个域中,这将保留原始消息、堆栈和自定义错误对象。异步处理JavaScript在运行异步代码时,以下异常处理会导致问题://scripts/asyncHandler.jsfunctionasyncHandler(fn){try{//ThisripsthepotentialbombfromthecurrentcontextsetTimeout(function(){fn();},1);}catch(e){}}检查单元测试的问题://tests/scripts/asyncHandlerTest.jsit('doesnotcatchexceptionswitherrors',function(){//炸弹varfn=function(){thrownewTypeError('typeerror');};//检查异常没有被捕获should.doesNotThrow(function(){asyncHandler(fn);});});这个异常没有被捕获,我们通过单元测试验证这一点。尽管代码包含try...catch,但try...catch语句仅在单个执行上下文中起作用。当抛出异常时,解释器已经跳出try...catch,所以不处理异常。Ajax调用也是如此。因此,一种解决方案是在异步回调中捕获异常:setTimeout(function(){try{fn();}catch(e){//Handlethisasyncerror}},1);这种方法效果会更好,但仍有很大的改进空间。首先,这些try...catch块在整个区域中纠缠在一起。事实上,V8浏览器引擎不鼓励在函数内部使用try...catch块。V8是Chrome浏览器和Node.js中使用的JavaScript引擎。一种方法是将try...catch块移动到调用堆栈的顶部,但这不适合异步代码编程。由于可以在任何上下文中进行全局错误处理,如果添加一个window对象进行错误处理,可以保证代码的DRY和SOLID原则。同时,全局的错误处理也可以保证你的异步代码是干净的。这是服务器上异常处理报告的内容。请注意,输出会因浏览器而异。从错误处理可以看出,错误来自于异步代码的setTimeout()函数。结语在做错误处理时,不要隐瞒问题,要及时发现问题,运用各种方法追根溯源,才能解决问题。虽然在写代码的时候难免会时不时地埋下错误,但是我们也不必太过为错误感到羞愧。及时解决发现的问题,避免出现更大的问题,正是我们现在需要做的。原文链接:https://www.sitepoint.com/proper-error-handling-javascript/转载请注明出处:葡萄城控
