译者按: 错误是无法避免的,妥善处理它才是最重要的!

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

如果你相信墨菲定律的话,任何事情如果会出问题,那么就一定会出问题。对于代码,即使我们有100%的自信没有问题,依然有可能出问题。在这篇文章,我们来研究如何处理JavaScript的错误。我会先介绍坏的处理方式、好的处理方式,最终介绍异步代码和Ajax。

个人感觉,事件驱动的编程设计使得JavaScript语言非常的丰富灵活。我们设想浏览器就是事件驱动机器,错误同样由它的驱动产生。当一个错误触发,导致某个事件被抛出。从理论上说,错误在JavaScript中就是事件。

如果你对此感到陌生,那么暂且不管它。在这篇文章中,我主要关注浏览器端的JavaScript。

这篇文章基于JavaScript中的错误处理部分的概念。如果你还不熟悉,我建议你先阅读一下。

Demo演示

我们使用的Demo可以在GitHub下载,程序运行起来会呈现如下页面:

所有的按钮都会触发错误,抛出TypeError。下面是该模块的定义:

// scripts/error.js
 
function error() {
var foo = {};
return foo.bar();
}

error()中定义了一个空对象foo,因此调用foo.bar()会因为未被定义而报错。我们使用单元测试来验证一下:

// tests/scripts/errorTest.js
 
it('throws a TypeError', function () {
should.throws(error, TypeError);
});

我们使用了Mocha配合Should.js做单元测试。

当你克隆了代码库并安装了依赖包以后,你可以使用npm t来执行测试。当然,你也可以执行某个测试文件,比如:./node_modules/mocha/bin/mocha tests/scripts/errorTest.js

相信我,像JavaScript这样的动态语言来说,不管谁都很容易遇到这样的错误。

坏的处理方式

我已经将按钮对应的处理事件函数抽象得简单一点,如下所示:

// scripts/badHandler.js
 
function badHandler(fn) {
try {
return fn();
} catch (e) { }
return null;
}

badHandler接收一个fn作为回调函数,该回调函数在badHandler中被调用。我们编写相应的单元测试:

// tests/scripts/badHandlerTest.js
 
it('returns a value without errors', function() {
var fn = function() {
return 1;
};
 
var result = badHandler(fn);
 
result.should.equal(1);
});
 
it('returns a null with errors', function() {
var fn = function() {
throw new Error('random error');
};
 
var result = badHandler(fn);
 
should(result).equal(null);
});

你会发现,如果出现异常,badHandler只是简单的返回null。如果配合完整的代码,你会发现问题所在:

// scripts/badHandlerDom.js
 
(function (handler, bomb) {
var badButton = document.getElementById('bad');
 
if (badButton) {
badButton.addEventListener('click', function () {
handler(bomb);
console.log('Imagine, getting promoted for hiding mistakes');
});
}
}(badHandler, error));

如果出错的时候将其try-catch,然后仅仅返回null,我根本找不到哪里出错了。这种安静失败(fail-silent)策略可能导致UI紊乱也可能导致数据错乱,并且在Debug的时候可能花了几个小时却忽略了try-catch里面的代码才是致祸根源。如果代码复杂到有多层次的调用,简直不可能找到哪里出了错。因此,我们不建议使用安静失败策略,我们需要更加优雅的方式。

不坏但很烂的方式

// scripts/uglyHandler.js
 
function uglyHandler(fn) {
try {
return fn();
} catch (e) {
throw new Error('a new error');
}
}

它处理错误的方式是抓到错误e,然后抛出一个新的错误。这样做的确优于之前安静失败的策略。如果出了错,我可以一层层找回去,直到找到原本抛出的错误e。简单的抛出一个Error('a new error')信息量比较有限,不精确,我们来自定义错误对象,传出更多信息:

// scripts/specifiedError.js
 
// Create a custom error
var SpecifiedError = function SpecifiedError(message) {
this.name = 'SpecifiedError';
this.message = message || '';
this.stack = (new Error()).stack;
};
 
SpecifiedError.prototype = new Error();
SpecifiedError.prototype.constructor = SpecifiedError;
 
// scripts/uglyHandlerImproved.js
 
function uglyHandlerImproved(fn) {
try {
return fn();
} catch (e) {
throw new SpecifiedError(e.message);
}
}
 
// tests/scripts/uglyHandlerImprovedTest.js
 
it('returns a specified error with errors', function () {
var fn = function () {
throw new TypeError('type error');
};
 
should.throws(function () {
uglyHandlerImproved(fn);
}, SpecifiedError);
});

现在,这个自定义的错误对象包含了原本错误的信息,因此变得更加有用。但是因为再度抛出来,依然是未处理的错误。

截获异常

一个思路是对所有的函数用try...catch包围起来:

function main(bomb) {
try {
bomb();
} catch (e) {
// Handle all the error things
}
}

但是,这样的代码将会变得非常臃肿、不可读,而且效率低下。是否还记得?在本文开始我们有提到在JavaScript中异常不过也是一个事件而已,幸运的是,有一个全局的异常事件处理方法(onerror)。

// scripts/errorHandlerDom.js
 
window.addEventListener('error', function (e) {
var error = e.error;
console.log(error);
});

获取堆栈信息

你可以将错误信息发送到服务器:

// scripts/errorAjaxHandlerDom.js
 
window.addEventListener('error', function (e) {
var stack = e.error.stack;
var message = e.error.toString();
 
if (stack) {
message += '\n' + stack;
}
 
var xhr = new XMLHttpRequest();
xhr.open('POST', '/log', true);
// Fire an Ajax request with error details
xhr.send(message);
});

为了获取更详细的报错信息,并且省去处理数据的麻烦,你也可以使用fundebug的JavaScript监控插件三分钟快速接入bug监控服务。

下面是服务器接收到的报错消息:

如果你的脚本是放在另一个域名下,如果你不开启CORS,除了Script error.,你将看不到任何有用的报错信息。如果想知道具体解法,请参考:Script error.全面解析

异步错误处理

由于setTimeout异步执行,下面的代码异常将不会被try...catch捕获:

// scripts/asyncHandler.js
 
function asyncHandler(fn) {
try {
// This rips the potential bomb from the current context
setTimeout(function () {
fn();
}, 1);
} catch (e) { }
}

try...catch语句只会捕获当前执行环境下的异常。但是在上面异常抛出的时候,JavaScript解释器已经不在try...catch中了,因此无法被捕获。所有的Ajax请求也是这样。

我们可以稍微改进一下,将try...catch写到异步函数的回调中:

setTimeout(function () {
try {
fn();
} catch (e) {
// Handle this async error
}
}, 1);

不过,这样的套路会导致项目中充满了try...catch,代码非常不简洁。并且,执行JavaScript的V8引擎不鼓励在函数中使用try...catch。好在,我们不需要这么做,全局的错误处理onerror会捕获这些错误。

结论

我的建议是不要隐藏错误,勇敢地抛出来。没有人会因为代码出现bug导致程序崩溃而羞耻,我们可以让程序中断,让用户重来。错误是无法避免的,如何去处理它才是最重要的。

版权声明:
转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/11/27/proper-error-handling-javascript/

处理JavaScript异常的正确姿势的更多相关文章

  1. 用 JavaScript 刷 LeetCode 的正确姿势【进阶】

    之前写了篇文章 用JavaScript刷LeetCode的正确姿势,简单总结一些用 JavaScript 刷力扣的基本调试技巧.最近又刷了点题,总结了些数据结构和算法,希望能对各为 JSer 刷题提供 ...

  2. jquery选中radio或checkbox的正确姿势

    jquery选中radio或checkbox的正确姿势 Intro 前几天突然遇到一个问题,没有任何征兆的..,jquery 选中radio button单选框时,一直没有办法选中,后来查了许多资料, ...

  3. 使用 Java8 Optional 的正确姿势(转)

    我们知道 Java 8 增加了一些很有用的 API, 其中一个就是 Optional. 如果对它不稍假探索, 只是轻描淡写的认为它可以优雅的解决 NullPointException 的问题, 于是代 ...

  4. Golang错误和异常处理的正确姿势

    Golang错误和异常处理的正确姿势 错误和异常是两个不同的概念,非常容易混淆.很多程序员习惯将一切非正常情况都看做错误,而不区分错误和异常,即使程序中可能有异常抛出,也将异常及时捕获并转换成错误.从 ...

  5. 代码走查25条疑问 C# 跳转新的标签页 C#线程处理 .Net 特性 attribute 学习 ----自定义特性 看懂 ,学会 .NET 事件的正确姿势-简单版

    代码走查25条疑问   代码走查(Code Review) 是一个开发人员与架构师集中讨论代码的过程.通过代码走查可以提高代码的 质量,同时减少Bug出现的几率.但是在小公司中并没有代码走查的过程在这 ...

  6. [转] 使用 Java8 Optional 的正确姿势

    [From] https://unmi.cc/proper-ways-of-using-java8-optional/ 我们知道 Java 8 增加了一些很有用的 API, 其中一个就是 Option ...

  7. .NET Core技术研究-HttpContext访问的正确姿势

    将ASP.NET升级到ASP.NET Core之后,相信大家都会遇到HttpContext.Current无法使用的问题.这也是我们迁移ASP.NET Core必须解决的问题. 本文我们详细讨论一下, ...

  8. 判断是否为gif/png图片的正确姿势

    判断是否为gif/png图片的正确姿势 1.在能取到图片后缀的前提下 1 2 3 4 5 6 7 8 9 //假设这是一个网络获取的URL NSString *path = @"http:/ ...

  9. 在Linux(ubuntu server)上面安装NodeJS的正确姿势

    上一篇文章,我介绍了 在Windows中安装NodeJS的正确姿势,这一篇,我们继续来看一下在Linux上面安装和配置NodeJS. 为了保持一致,这里也列举三个方法 第一个方法:通过官网下载安装 h ...

随机推荐

  1. Akka-Cluster(6)- Cluster-Sharding:集群分片,分布式交互程序核心方式

    在前面几篇讨论里我们介绍了在集群环境里的一些编程模式.分布式数据结构及具体实现方式.到目前为止,我们已经实现了把程序任务分配给处于很多服务器上的actor,能够最大程度的利用整体系统的硬件资源.这是因 ...

  2. Kali学习笔记37:APPSCAN

    APPSCAN是一款商业Web扫描器,被IBM公司收购 功能和AVWS.Burp类似,不过界面更加简单 APPSCAN的使用比Burp等开源软件要简单很多 所以我这里写得内容比较少,不过它的功能还是很 ...

  3. OAuth2简易实战(四)-Github社交联合登录

    1. OAuth2简易实战(四)-Github社交联合登录 1.1. 用到的第三方插件 https://github.com/spring-projects/spring-social-github ...

  4. dubbo实用知识点总结(三)

    1. 服务降级 2. 优雅停机 3. 主机绑定 4. 访问日志 5. Multicast注册中心 6. zookeeper注册中心 7. 推荐用法 8. 容量规划 9. 基准测试工具包

  5. FileZilla 使用笔记

    FileZilla 使用了三年了,一些功能其实还没有主动去发现,这次接着项目忙完的空闲时间整理一下 Tips,提高工作效率,方便以后查阅. 一.好用的功能 1.Site Manager - 站点管理器 ...

  6. java中调用三方接口post传参时map和jsonobject的区别转换

    post方法名及参数为:(具体方法可参考https://www.cnblogs.com/mufengforward/p/10510337.html) public static String doPo ...

  7. 2.Git基础-仓库的获取方式与Git文件的状态变化周期(生命周期)

    1.仓库的获取 Git仓库的获取有两种方式: 1.从现有目录或者是项目中导入所有文件到Git中. 2.从一个服务器clone一个现有的Git仓库. 如果使用第一种方式,只需要在你希望被Git进行管理的 ...

  8. python之使用位运算符实现加法运算

    一哥们去笔试,回来后跟我说了一通面试题,其中有一道题让我很感兴趣: 不使用+号实现加法运算 刚听到后,一脸懵逼,不使用+号怎么算? 问了朋友他也没做这题,不过仔细想了下,不使用+号,是否可以使用其他运 ...

  9. Google的java工具类Guava

    前言 google开发java项目肯定也不想重复造轮子,所以肯定也有工具类,就是它了:Guava 我将举例几个实际的例子,发挥这个工具类好用的功能.更多的方法和功能,还有内部的实现可以直接参考http ...

  10. 前端JS 与 后台C# 之间JSON序列化与反序列化(笔记)

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.   在 前端浏览器 和 后端服务器 之间通常会使用 JSON格式的数据 来进行数据交互,而JSON格式的 ...