前言

 编程时我们往往拿到的是业务流程正确的业务说明文档或规范,但实际开发中却布满荆棘和例外情况,而这些例外中包含业务用例的例外,也包含技术上的例外。对于业务用例的例外我们别无它法,必须要求实施人员与用户共同提供合理的解决方案;而技术上的例外,则必须由我们码农们手刃之,而这也是我想记录的内容。

 我打算分成《前端魔法堂——异常不仅仅是try/catch》和《前端魔法堂——调用栈,异常实例中的宝藏》两篇分别叙述内置/自定义异常类,捕获运行时异常/语法异常/网络请求异常/PromiseRejection事件,什么是调用栈和如何获取调用栈的相关信息。

 是不是未出发就已经很期待呢?好吧,大家捉紧扶手,老司机要开车了_

概要

 本篇将叙述如下内容:

  1. 异常还是错误?它会如何影响我们的代码?
  2. 内置异常类型有哪些?
  3. 动手写自己的异常类型吧!
  4. 捕获“同步代码”中的"运行时异常",用try/catch就够了。
  5. "万能"异常捕获者window.onerror,真的万能吗?
  6. Promise.reject也抛异常,怎么办?
  7. 404等网络请求异常真心要后之后觉吗?

一.异常还是错误?它会如何影响我们的代码?

 在学习Java时我们会被告知异常(Exception)和错误(Error)是不一样的,异常是不会导致进程终止从而可以被修复(try/catch),但错误将会导致进程终止因此不能被修复。当对于JavaScript而言,我们要面对的仅仅有异常(虽然异常类名为Error或含Error字样),异常的出现不会导致JavaScript引擎崩溃,最多就是让当前执行的任务终止而已。

 上面说到异常的出现最多就是让当前执行的任务终止,到底是什么意思呢?这里就涉及到Event Loop的原理了,下面我尝试用代码大致说明吧。

<script>
// 1.当前代码块将作为一个任务压入任务队列中,JavaScript线程会不断地从任务队列中提取任务执行;
// 2.当任务执行过程中报异常,且异常没有捕获处理,则会一路沿着调用栈从顶到底抛出,最终终止当前任务的执行;
// 3.JavaScript线程会继续从任务队列中提取下一个任务继续执行。
function a(){throw Error("test")}
function b(){a()}
b()
console.log("永远不会执行!")
</script>
<script>
// 下一个任务
console.log("你有你抛异常,我照样执行!")
</script>

二.内置异常类型有哪些?

 说到内置异常类那么必先提到的就是Error这个祖先类型了,其他所有的内置异常类和自定义类都必须继承它。而它的标准属性和方法就以下这寥寥几个而已

@prop {String} name - 异常名称
@prop {String} message - 供人类阅读的异常信息
@prop {Function} constructor - 类型构造器
@method toString():String - 输出异常信息

 由于标准属性实在太少,无法提供更有效的信息供开发者定位异常发生的位置和重现事故现场,因此各浏览器厂家均手多多的自己增加些属性,然后逐渐成了事实标准。

@prop {String} fileName - 异常发生的脚本URI
@prop {number} lineNumber - 异常发生的行号
@prop {number} columnNumber - 异常发生的列号
@prop {String} stack - 异常发生时的调用栈信息,IE10及以上才支持
@method toSource():String - 异常发生的脚本内容

另外巨硬还新增以下两个属性

@prop {String} description - 和message差不多
@prop {number} number - 异常类型的编号,巨硬为每个异常设置了一个唯一的编号

 那么现在我要实例化一个Error对象,只需调用Error()new Error()即可;若想同时设置message,则改为Error("test")new Error("test")。其实Error的构造函数签名是这样的

@constructor
@param {String=} message - 设置message属性
@param {String=} fileName - 设置fileName属性
@param {number=} lineNumber - 设置lineNUmber属性

现在我们看看具体有哪些内置的异常类型吧!

  1. EvalError,调用eval()时发生的异常,已被废弃只用于向后兼容而已
  2. InternalError,JavaScript引擎内部异常,FireFox独门提供的!
  3. RangeError,当函数实参越界时发生,如ArrayNumber.toExponential,Number.toFixedNumber.toPrecision时入参非法时。
  4. ReferenceError,当引用未声明的变量时发生
  5. SyntaxError,解析时发生语法错误
  6. TypeError,当值不是所期待的类型时,null.f()也报这个错
  7. URIError,当传递一个非法的URI给全局URI处理函数时发生,如decodeURIComponent('%'),即decodeURIComponentdecodeURI,encodeURIComponentencodeURI

三.动手写自己的异常类型吧!

 关于在StackOverflow上早有人讨论如何自定义异常类型了参考

于是我们顺手拈来即可

function MyError(message, fileName, lineNumber){
if (this instanceof MyError);else return new MyError(message, fileName, lineNumber)
this.message = message || ""
if (fileName){ this.fileName = fileName }
if (lineNumber){ this.lineNumber = lineNumber }
} var proto = MyError.prototype = Object.create(Error.prototype)
proto.name = "MyError"
proto.constructor = MyError

cljs实现如下

(defn ^export MyError [& args]
(this-as this
(if (instance? MyError this)
(let [ps ["message" "fileName" "lineNumber"]
idxs (-> (min (count args) (count ps)) range)]
(reduce
(fn [accu i]
(aset accu (nth ps i) (nth args i))
accu)
this
idxs))
(apply new MyError args)))) (def proto
(aset MyError "prototype" (.create js/Object (.-prototype Error))))
(aset proto "name" "MyError")
(aset proto "constructor" MyError)

四.捕获“同步代码”中的"运行时异常",用try/catch就够了

 为了防止由于异常的出现,导致正常代码被略过的风险,我们习惯采取try/catch来捕获并处理异常。

try{
throw Error("unexpected operation happen...")
}
catch (e){
console.log(e.message)
}

cljs写法

(try
(throw (Error. "unexpected operation happen...")
(catch e
(println (.-message e)))))

 很多时我们会以为这样书写就万事大吉了,但其实try/catch能且仅能捕获“同步代码”中的"运行时异常"。

1."同步代码"就是说无法获取如setTimeoutPromise等异步代码的异常,也就是说try/catch仅能捕获当前任务的异常,setTimeout等异步代码是在下一个EventLoop中执行。

// 真心捕获不到啊亲~!
try{
setTimeout(function(){
throw Error("unexpected operation happen...")
}, 0)
} catch(e){
console.log(e)
}

2."运行时异常"是指非SyntaxError,也就是语法错误是无法捕获的,因为在解析JavaScript源码时就报错了,还怎么捕获呢~~

// 非法标识符a->b,真心捕获不到啊亲~!
try{
a->b = 1
} catch(e){
console.log(e)
}

 这时大家会急不可待地问:“异步代码的异常咋办呢?语法异常咋办呢?”在解答上述疑问前,我们先偏离一下,稍微挖挖throw语句的特性。

throw后面可以跟什么啊?

 一般而言我们会throw一个Error或其子类的实例(如throw Error()),其实我们throw任何类型的数据(如throw 1,throw "test",throw true等)。但即使可以抛出任意类型的数据,我们还是要坚持抛出Error或其子类的实例。这是为什么呢?

try{
throw "unexpected operation happen..."
} catch(e){
console.log(e)
} try{
throw TypeError("unexpected operation happen...")
} catch(e){
if ("TypeError" == e.name){
// Do something1
}
else if ("RangeError" == e.name){
// Do something2
}
}

 原因显然易见——异常发生时提供信息越全越好,更容易追踪定位重现问题嘛!

五."万能"异常捕获者window.onerror,真的万能吗?

 在每个可能发生异常的地方都写上try/catch显然是不实际的(另外还存在性能问题),即使是罗嗦如Java我们开发时也就是不断声明throws,然后在顶层处理异常罢了。那么,JavaScript中对应的顶层异常处理入口又在哪呢?木有错,就是在window.onerror。看看方法签名吧

@description window.onerror处理函数
@param {string} message - 异常信息"
@param {string} source - 发生异常的脚本的URI
@param {number} lineno - 发生异常的脚本行号
@param {number} colno - 发生异常的脚本列号
@param {?Error} error - Error实例,Safari和IE10中没有这个实参

 这时我们就可以通过它捕获除了try/catch能捕获的异常外,还可以捕获setTimeout等的异步代码异常,语法错误。

window.onerror = function(message, source, lineno, colno, error){
// Do something you like.
} setTimeout(function(){ throw Error("oh no!") }, 0)
a->b = 1

 这样就满足了吗?还没出大杀技呢——屏蔽异常、屏蔽、屏~~

 只有onerror函数返回true时,异常就不会继续向上抛(否则继续上抛就成了Uncaught Error了)。

// 有异常没问题啊,因为我看不到^_^
window.onerror = function(){return true}

 现在回到标题的疑问中,有了onerror就可以捕获所有异常了吗?答案又是否定的(我的娘啊,还要折腾多久啊~0~)

  1. Chrome中对于跨域脚本所报的异常,虽然onerror能够捕获,但统一报Script Error。若要得到正确的错误信息,则要配置跨域资源共享CORS才可以。
  2. window.onerror实际上采用的事件冒泡的机制捕获异常,并且在冒泡(bubble)阶段时才触发,因此像网络请求异常这些不会冒泡的异常是无法捕获的。
  3. Promise.reject产生的未被catch的异常,window.onerror也是无能为力。

六.Promise.reject也抛异常,怎么办?

 通过Promise来处理复杂的异步流程控制让我们得心应手,但倘若其中出现异常或Promise实例状态变为rejected时,会是怎样一个状况,我们又可以如何处理呢?

Promise是如何标识异常发生的?

 Promise实例的初始化状态是pending,而发生异常时则为rejected,而导致状态从pending转变为rejected的操作有

  1. 调用Promise.reject类方法
  2. 在工厂方法中调用reject方法
  3. 在工厂方法或then回调函数中抛异常
// 方式1
Promise.reject("anything you want") // 方式2
new Promise(function(resolve, reject) { reject("anything you want") }) // 方式3
new Promise(function{ throw "anything you want" })
new Promise(function(r) { r(Error("anything you want" ) }).then(function(e) { throw e })

 当Promise实例从pending转变为rejected时,和之前谈论到异常一样,要么被捕获处理,要么继续抛出直到成为Uncaught(in promise) Error为止。

异常发生前就catch

 若在异常发生前我们已经调用catch方法来捕获异常,那么则相安无事

new Promise(function(resolve, reject){
setTimeout(reject, 0)
}).catch(function(e){
console.log("catch")
return "bingo"
}).then(function(x){
console.log(x)
}) // 回显 bingo

专属于Promise的顶层异常处理

 若在异常发生前我们没有调用catch方法来捕获异常,还是可以通过windowunhandledrejection事件捕获异常的

window.addEventListener("unhandledrejection", function(e){
// Event新增属性
// @prop {Promise} promise - 状态为rejected的Promise实例
// @prop {String|Object} reason - 异常信息或rejected的内容 // 会阻止异常继续抛出,不让Uncaught(in promise) Error产生
e.preventDefault()
})

迟来的catch

 由于Promise实例可异步订阅其状态变化,也就是可以异步注册catch处理函数,这时其实已经抛出Uncaught(in promise) Error,但我们依然可以处理

var p = new Promise(function(resolve, reject){
setTimeout(reject, 0)
})
setTimeout(function(){
p.catch(function(e){
console.log("catch")
return "bingo"
})
}, 1000)

 另外,还可以通过windowrejectionhandled事件监听异步注册catch处理函数的行为

window.addEventListener("rejectionhandled", function(e){
// Event新增属性
// @prop {Promise} promise - 状态为rejected的Promise实例
// @prop {String|Object} reason - 异常信息或rejected的内容 // Uncaught(in promise) Error已经抛出,所以这句毫无意义^_^
e.preventDefault()
})

注意:只有抛出Uncaught(in promise) Error后,异步catch才会触发该事件。

七.404等网络请求异常真心要后之后觉吗?

 也许我们都遇到<img src="./404.png">报404网络请求异常的情况,然后测试或用户保障怎么哪个哪个图标没有显示。其实我们我们可以通过以下方式捕获这类异常

window.addEventListener("error", function(e){
// Do something
console.log(e.bubbles) // 回显false
}, true)

 由于网络请求异常不会冒泡,因此必须在capture阶段捕获才可以。但还有一个问题是这种方式无法精确判断异常的HTTP状态是404还是500等,因此还是要配合服务端日志来排查分析才可以。

总结

 对异常和如何捕获异常仅仅是前端智能监控中的一小撮知识点,敬请期待后续另一小撮知识点《前端魔法堂——调用栈,异常实例中的宝藏》吧:D

 尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/7685144.html _肥仔John

参考

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError

https://stackoverflow.com/questions/8504673/how-to-detect-on-page-404-errors-using-javascript

前端魔法堂——异常不仅仅是try/catch的更多相关文章

  1. (后端)异常不仅仅是try/catch

    前言  编程时我们往往拿到的是业务流程正确的业务说明文档或规范,但实际开发中却布满荆棘和例外情况,而这些例外中包含业务用例的例外,也包含技术上的例外.对于业务用例的例外我们别无它法,必须要求实施人员与 ...

  2. 处理异常的 try,catch

    try catch finally 1.将预见可能引发异常的代码包含在try语句块中. 2.如果发生了异常,则转入catch的执行.catch有几种写法: catch 这将捕获任何发生的异常. cat ...

  3. Java异常之try,catch,finally,throw,throws

    Java异常之try,catch,finally,throw,throws 你能区分异常和错误吗? 我们每天上班,正常情况下可能30分钟就能到达.但是由于车多,人多,道路拥挤,致使我们要花费更多地时间 ...

  4. Java解决异常之try、catch、finally、throw、throws&log4j记录日志步骤

    知识点一.多重catch引发多种类型的异常排列catch 语句的顺序:先子类后父类 发生异常时按顺序逐个匹配只执行第一个与异常类型匹配的catch语句二.异常分类异常分为运行时异常和检测异常运行时异常 ...

  5. Velocity魔法堂系列一:入门示例(转)

    Velocity魔法堂系列一:入门示例 一.前言 Velocity作为历史悠久的模板引擎不单单可以替代JSP作为Java Web的服务端网页模板引擎,而且可以作为普通文本的模板引擎来增强服务端程序文本 ...

  6. java:异常机制(try,catch,finally,throw,throws,自定义异常)

    * String类中的格式化字符串的方法: * public static String format(String format, Object... args):使用指定的格式字符串和参数返回一个 ...

  7. 异常依然执行{try..catch语句块..}的后续代码

    测试异常依然执行{try..catch语句块..}的后续代码: private static Integer testThrows() throws Exception{ Integer result ...

  8. JS魔法堂之实战:纯前端的图片预览

    一.前言 图片上传是一个普通不过的功能,而图片预览就是就是上传功能中必不可少的子功能了.在这之前,我曾经通过订阅input[type=file]元素的onchange事件,一旦更改路径则将图片上传至服 ...

  9. Java中有多个异常, 如何确定捕获顺序(多个catch),先从上到下执行,判断异常的大小,如果包含捕到异常,就进入这个catch,后面的就不再执行

    Java中异常的捕获顺序(多个catch)( Java代码 import java.io.IOException; public class ExceptionTryCatchTest { publi ...

随机推荐

  1. 个人作业3——个人总结(Alpha阶段)

    个人总结 在Alpha冲刺阶段中,我们团队基本完成了项目的大致基础框架,还有很多不足需要更多的时间来让我们做得更好. 对我个人而言,Alpha冲刺阶段是一个强度很大的阶段,每天都在吸收新的知识,团队也 ...

  2. Java学习9——面向对象

    (重点:内存分析) 类的定义 //用class关键字定义一个类 class Person { //成员变量定义 private int id; private int age = 20; //方法定义 ...

  3. 【Alpha】——Third Scrum Meeting

    一.今日站立式会议照片 二.每个人的工作 成员 昨天已完成的工作 今天计划完成的工作 李永豪 基本完成添加功能 继续完善添加功能 郑靖涛 基本完成删除功能 继续完善删除功能 杨海亮 基本完成查找功能 ...

  4. Java课程设计 购物车系统(个人博客) 201521123052 蓝锦明

    1. 团队课程设计博客链接 课程设计团队博客 2. 个人负责模块或任务说明 (1)制作图形菜单引导界面 (2)定义各获取和输出类函数 3. 自己的代码提交记录截图 4. 自己负责模块或任务详细说明 i ...

  5. ajax之XML简介

    XML:可扩展标记语言,传输数据 HTML:超文本标记语言,显示数据   XML:标签构成 特点: 1.标签名可以自定义 2.必须有一个根(有且只有一个) 3.有开始标签就必须有结束标签 4.大小写敏 ...

  6. [06] Java的数据类型

    1.基本数据类型 1.1 基本数据类型 byte.chart.short.int.long.float.double.boolean 共8种数据类型为基本数据类型: 数据类型     位数     取 ...

  7. 《Java I/O 从0到1》 - 第Ⅱ滴血 “流”

    前言 <Java I/O 从0到1>系列上一章节,介绍了File 类,这一章节介绍的是IO的核心 输入输出.I/O类库常使用流这个抽象概念.代表任何有能力产出数据的数据源对象或者是有能力接 ...

  8. String类的一些转换功能(6)

    1:把字符串转换成字节数组 getBytes() 如: String s = "你好啊!" //编码 byte [] arr = s.getBytes();//这里默认编码格式是g ...

  9. readfile & file_get_contents异同

    记录一下:应用memcache时,准备把整个文件缓存到内存中,遇到了比较奇怪的事情,因为最初使用readfile来读取文件,结果这个函数返回一个字节数,而不是一个字符串,于是文件没办法再输出,最后使用 ...

  10. 翻译连载 | 第 9 章:递归(下)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...