A Guide to Proper Error Handling in JavaScript

这是关于JavaScript中异常处理的故事。如果你相信 墨菲定律 ,那么任何事情都可能出错,不,一定会出错!这篇文章中我们来看下JavaScript中的出错处理。文章会覆盖异常处理使用的正反例,然后看下ajax的异步处理。

JavaScript的事件驱动机制让JavaScript更加丰富,浏览器好比就是一个事件驱动的机器,错误也是一种事件。当一个错误发生时,一个事件就在某个点抛出。理论上,有人会说错误是Javascript中的简单事件。如果你觉得是这样,那你就要好好去看看了。另外这篇文章只关注浏览器端的JavaScript的情况。

这篇文章将在《 Exceptional Exception Handling in JavaScript 》这篇文章的概念基础上进行解释。解释起来就是,当发生错误时,JavaScript会去调用栈检查异常事件。如果你对此不熟悉建议先去看看基础的东西。我们的目的是探索处理异常的必要性,接下来你会看到一个 try...catch 块语句,你要认真思考。

例子

例子的代码在 github 上,而且最终展示成这样:

所有的按钮点击是都会触发"炸弹",这个炸弹模拟了一个抛出的 TypeError 异常。下面是这个模块单元测试的定义:

  1. function error() {
  2. var foo = {};
  3. return foo.bar();
  4. }

开始时,这个函数定义了一个空的对象 foo ,注意 bar() 没有在任何地方定义,我们用一个测试用例来看下它是如何引爆炸弹的。

  1. it('throws a TypeError', function () {
  2. should.throws(target, TypeError);
  3. });

这个单元测试是用 mochashould.js 写的。 mocha 是一个测试框架, should.js 是一个断言库。如果你熟悉它们后,你会感觉写起来很爽。测试一般使用 it('description') 开始,然后在 should 中使用 pass/fail 结束。好消息是测试用例可以在node端运行而不需要浏览器。我建议多关注这些测试,因为它们能帮助我们提升代码的质量。

正如所显示的, error() 定义了一个空的对象,然后尝试访问一个方法,因为 bar() 方法在对象中不存在而会抛出一个异常。使用JavaScript这种动态语言运行一定会出错。

错误的方式

对于一些错误的处理,我从按钮的而事件中抽离出异常处理的方式,下面是单元测试函数的代码:

  1. function badHandler(fn) {
  2. try {
  3. return fn();
  4. } catch (e) { }
  5. return null;
  6. }

这个处理函数接收一个 fn 回调函数作为输入,这个函数然后在处理器函数里面被调用,单元测试如下:

  1. it('returns a value without errors', function() {
  2. var fn = function() {
  3. return 1;
  4. };
  5. var result = target(fn);
  6. result.should.equal(1);
  7. });
  8. it('returns a null with errors', function() {
  9. var fn = function() {
  10. throw Error('random error');
  11. };
  12. var result = target(fn);
  13. should(result).equal(null);
  14. });

如你所见,这个糟糕的处理函数如果有地方出错就会返回null,回调函数 fn() 可以指向一个正确的方法或者一个异常,下面的点击处理函数会显示最终的处理结果。

  1. (function (handler, bomb) {
  2. var badButton = document.getElementById('bad');
  3. if (badButton) {
  4. badButton.addEventListener('click', function () {
  5. handler(bomb);
  6. console.log('Imagine, getting promoted for hiding mistakes');
  7. });
  8. }
  9. }(badHandler, error));

可恶的是,这里返回了一个null,当我想找哪里出了问题时整个人都蒙逼了。这种失败沉默的方式会影响用户体验和数据混乱。更令人崩溃的是,我花了几个小时来进行debugg,但却没有使用 try-catch ,这个糟糕的处理函数吞没了错误并认为它没有问题, 这样继续执行下去不会降低代码质量,但是隐藏的错误未来会让你花几个小时来debugg。在一个多层的深调用时,基本上不可能发现哪里出了问题。而在这些少数的地方使用 try-catch 是正确的。但是一旦进入错误处理函数,就比较糟糕了。

失败沉默策略会让你不容易发现错误所在,JavaScript提供了一个更优雅的方式来处理这些问题。

比较差的方式

继续,是时候说下一个稍微好点的方法了。我先跳过事件绑定到dom上的部分。这个函数处理和刚刚我们看到的没什么不同。所不同的是单元测试中它处理异常的方式。

  1. function uglyHandler(fn) {
  2. try {
  3. return fn();
  4. } catch (e) {
  5. throw Error('a new error');
  6. }
  7. }
  8. it('returns a new error with errors', function () {
  9. var fn = function () {
  10. throw new TypeError('type error');
  11. };
  12. should.throws(function () {
  13. target(fn);
  14. }, Error);
  15. });

这里定义在原来的基础上改进了。这里异常事件在调用栈中进行冒泡,我喜欢的是现在错误现在会离开方便debugg的调用栈。在这个异常中,解释器会遍历整个栈寻找另一个错误处理函数。这样就可以有机会在调用栈的顶端处理这些错误。不幸的是,因为这个方法,我不知道错误是从哪个地方抛出来的。所以我又得反向遍历这个栈找到错误异常的源头。但至少我知道某个地方出错了,并能找到是哪个地方抛出的错误。

离开调用栈

所以,一个抛出异常处理的方法是直接调用栈的顶端使用 try-catch ,就像:

  1. function main(bomb) {
  2. try {
  3. bomb();
  4. } catch (e) {
  5. // Handle all the error things
  6. }
  7. }

但是,记住我说的浏览器是事件驱动的。是的,JavaScript中的错误也不过是一个事件。解释器在当前的执行上下文中执行后释放。结果是,我们可以利用一个 onerror 的全局异常事件处理函数,它大概是这样的:

  1. if(window.addEventListener){
  2. window.addEventListener('error', function (e) {
  3. var error = e.error;
  4. console.log(error);
  5. });
  6. }else if(window.attachEvent){
  7. window.attachEvent('onerror', function (e) {
  8. var error = e.error;
  9. console.log(error);
  10. });
  11. }else{
  12. window.onerror = function(e){
  13. var error = e.error;
  14. console.log(error);
  15. }
  16. }

感觉这种方式应该不错,可以尝试一下

这个处理函数能捕获任何执行上下文中的错误异常。包括任何类型的任何错误。而且它能定位到代码中的错误处理。就像其它任何事件一样,你能捕获特定错误的具体信息。这样能使异常处理器只专注于一件事情,如果你允许这样做的话。这些处理函数也可以在任何时候注册,解释器会尽可能的遍历更多的处理函数,我们再也不用使用 try-catch 块这种带有瑕疵的debug方式了。尤其是在对待像JavaScript这类事件驱动机制的语言时,onerror的优势就更大了

现在我们可以使用全局处理函数来离开栈了,我们可以用来干什么呢。毕竟,调用栈还是存在的。

捕获栈信息

调用栈在定位问题时超级有用。好消息是,浏览器提供了这个信息。理所当然,查看错误异常中的栈属性不是标准的一部分,但是只在新的浏览器中可以使用。所以,你就可以这样来把错误日志发送给服务器了。

  1. window.addEventListener('error', function (e) {
  2. var stack = e.error.stack;
  3. var message = e.error.toString();
  4. if (stack) {
  5. message += '\n' + stack;
  6. }
  7. var xhr = new XMLHttpRequest();
  8. xhr.open('POST', '/log', true);
  9. xhr.send(message);
  10. });

可能从代码样例来说不是很明显,但是上面的代码一定会出错。上面提到了,每个处理函数都只处理一个功能。我关心的是这些信息是怎样被服务器捕获的。如下:

这些信息来自FireFox 46的开发版本,通过一个正确的错误处理函数,记录了出错的情况。这里没必要隐藏错误,我可以看到什么地方出现的什么错误。这样代码debugg就很爽了。这些信息也可以保存在持续化缓存中以便于以后分析。

调用栈对于debugg来说是很有用的,永远不要低估调用栈的力量。

异步处理

处理异步时,JavaScript的异步处理代码不在当前的指向上下文中,这意味着 try-catch 语句会有问题(不能捕获到异常):

  1. function asyncHandler(fn) {
  2. try {
  3. setTimeout(function () {
  4. fn();
  5. }, 1);
  6. } catch (e) { }
  7. }

单元测试的结果如下:

  1. it('does not catch exceptions with errors', function () {
  2. var fn = function () {
  3. throw new TypeError('type error');
  4. };
  5. failedPromise(function() {
  6. target(fn);
  7. }).should.be.rejectedWith(TypeError);
  8. });
  9. function failedPromise(fn) {
  10. return new Promise(function(resolve, reject) {
  11. reject(fn);
  12. });
  13. }

我必须用promise包含这个处理器来获取这个错误。注意的是,一个未被处理的异常发生时,尽管我将代码使用 try-catch 包含起来了,是的, try-catch 只能在单一的作用域内有效。在一个异常被抛出的同时,解释器就会从 try-catch 中离开,ajax也是一样的。所以有两种选择,一种是在异步调用里面捕获异常:

  1. setTimeout(function () {
  2. try {
  3. fn();
  4. } catch (e) {
  5. // Handle this async error
  6. }
  7. }, 1);

这种方法很有效,但是很多地方可以改进。首先, try-catch 块在这里用很混乱。实际上,之前是这么做的,但是有问题。另外,V8引擎不鼓励 在函数中使用try-catch (V8 是chrome和nodejs中的JavaScript引擎)。它们的建议是最外层写这些块。

所以我们该怎么办?我说过全局异常处理可以在任何执行上下文中执行,如果给window对象增加一个错误处理函数,就OK了。这样是不是既能处理捕获处理错误又能保持代码的优雅呢。全局的错误处理能让你的代码干净整洁。

下面是服务器收集到的错误日志,注意的是如果你使用同样的代码再不同浏览器上执行,你会看到收集到的日志也是不同的:

这个处理函数甚至告诉我们错误是从异步代码中抛出的吗,它告诉我们来至 setTimeout() 函数。

结论

总得来说,进行异常处理至少有两种方法。

  • 一个是失败沉默的方法,在错误发生时忽略错误不作为而不影响后面的继续执行。
  • 另一种是发生后迅速找到错误发生的地方。

明显我们知道那种方法更具有优势。我的选择是:不要隐藏错误。没人会因为你代码中有问题而鄙视你,用户多试一次是可以接受的。代码距离完美是很远的,错误也是不可避免的,重要的是你发现错误后会怎么做。

译者注:文章浅显的分析了错误处理的方式和一些正反案例,其实处理错误的最终目的还是提供前端代码的质量,关于错误处理上报可以参考下 badjs 的思路,基于现代前端开发模块化的基础,使用全局 onerrortry-catch 相结合的方式更能有效进行错误定位。


原文作者:Camilo Reyes

原译:ouven

原文地址:http://www.sitepoint.com/proper-error-handling-javascript/

这篇文章有点难度或者说不好理解

看自己的情况继续努力,过一段时间之后再来读一遍

[六字真言]5.咪.功力不足,学习前端JavaScript异常的更多相关文章

  1. [六字真言]2.嘛.异常定制和通用.md

    幻世当空 恩怨休怀 舍悟离迷 六尘不改 且怒且悲且狂哉! 最近一直在循环的一首歌! 丰富自己,比取悦他人更有力量.种下梧桐树,引得凤凰来.你若盛开,蝴蝶自来! 言归正传! 言归正传! 不要去大包大揽 ...

  2. [六字真言]6.吽.SpringMVC中上传大小异常填坑

    最近在讲课的时候,遇到了关于上传文件过大的时候浏览器无法响应的问题,配置了捕获异常,有的学生浏览器好使,有的学生浏览器不好用!莫名其妙! MaxUploadSizeExceededException进 ...

  3. [六字真言]4.叭.SpringMVC异常痛苦

    "叭",除畜生道劳役之苦: 在学过的三阶段的时候,我们对SpringMVC的异常处理,一直可以算是简单中透着暴力,不要不重视异常!真的很重要,不要让它处在尴尬的位置! 在二阶段或者 ...

  4. 从零开始学习前端JAVASCRIPT — 1、JavaScript基础

    1:定义:javascript是一种弱类型.动态类型.解释型的脚本语言. 弱类型:类型检查不严格,偏向于容忍隐式类型转换. 强类型:类型检查严格,偏向于不容忍隐式类型转换. 动态类型:运行的时候执行类 ...

  5. 从零开始学习前端JAVASCRIPT — 14、闭包与继承

    一.闭包 1 . 概念:闭包就是能够读取其他函数内部变量的函数.在JS中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解为”定义在一个函数内部的函数”. 2 . 闭包的特点 1)可以读取 ...

  6. 从零开始学习前端JAVASCRIPT — 12、JavaScript面向对象编程

    一.构造函数的使用 <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...

  7. 从零开始学习前端JAVASCRIPT — 11、JavaScript运动模型及轮播图效果、放大镜效果、自适应瀑布流

    未完待续...... 一.运动原理 通过连续不断的改变物体的位置,而发生移动变化. 使用setInterval实现. 匀速运动:速度值一直保持不变. 多物体同时运动:将定时器绑设置为对象的一个属性. ...

  8. 从零开始学习前端JAVASCRIPT — 4、JavaScript基础Math和Date对象的介绍

    Math对象的介绍 1:Math对象 Math 对象用于执行数学任务.并不像 Date 和 String 那样是对象的类,因此没有构造函数 Math().您无需创建它,通过把 Math 作为对象使用就 ...

  9. 从零开始学习前端JAVASCRIPT — 3、JavaScript基础string字符串介绍

    1:字符串 JS中的任何数据类型都可以当作对象来看.所以string既是基本数据类型,又是对象. 2:声明字符串 基本数据类型:var sStr = '字符串'; 对象的方法:var oStr = n ...

随机推荐

  1. Elasticsearch Query DSL 整理总结(一)—— Query DSL 概要,MatchAllQuery,全文查询简述

    目录 引言 概要 Query and filter context Match All Query 全文查询 Full text queries 小结 参考文档 引言 虽然之前做过 elasticse ...

  2. java File读取文件始终不存在的问题分析

    先上图: 如图,f1 始终能读到该文件,使用的是绝对路径 f2 却是相对路径. 感觉很奇怪,明明一模一样的代码为什么会产生不同的结果呢? 首先想到的是是不是有什么特殊字符.. 拿到notepad++中 ...

  3. 从字节码层面,解析 Java 布尔型的实现原理

    最近在系统回顾学习 Java 虚拟机方面的知识,其中想到一个很有意思的问题:布尔型在虚拟机中到底是什么类型? 要想解答这个问题,我们看 JDK 的源码是无法解决源码的,我们必须深入到 class 文件 ...

  4. 微软职位内部推荐-Senior Program Manager

    微软近期Open的职位: Title: Senior Program Manager – Bing Multimedia Relevance Group: Search Technology Cent ...

  5. 基于SSH框架的学生选课质量属性分析

    系统:学生选课系统 框架:SSH(Struts2+Spring+Hibernate) 我做的是基于SSH框架的学生选课系统.学生选课系统的特性:①系统响应时间短,能够快速调出课程数据供学生选课提交.② ...

  6. 20172319 《Java程序设计教程》第7周学习总结

    20172319 2018.04.11-16 <Java程序设计教程>第7周学习总结 目录 教材学习内容总结 教材学习中的问题和解决过程 代码调试中的问题和解决过程 代码托管 上周考试错题 ...

  7. [BUG随想录] 看不见的分隔符: Zero-width space

    今天在调试一段代码的时候,有一个输入不能为空的库函数抛出了异常(为空就会抛出异常,就是这么傲娇).自己暗骂了自己一番,怎么这么大意,于是追溯源头,开始寻找输入控制的地方.但是当我找到时我惊呆了,我明明 ...

  8. final-review

    小组名称:飞天小女警 项目名称:礼物挑选小工具 小组成员:沈柏杉(组长).程媛媛.杨钰宁.谭力铭 会议时间:12月2号12点 会议内容: 设想和目标 1.我们的软件要解决什么问题?是否定义得很清楚?是 ...

  9. PHP 使用GD 库绘制图像,无法显示的问题

    根据官方GD 库绘制图像文档样式 原基本样式 $width = 120; $height = 50; $img = @imagecreatetruecolor($width, $height) or ...

  10. sysbench的安装与简单使用

    1. 下载sysbench的文件 https://codeload.github.com/akopytov/sysbench/zip/1.0.15 2. 放进linux机器以及进行解压缩 unzip ...