编程时咱们每每拿到的是业务流程正确的业务说明文档或规范,但实际开发中却布满荆棘和例外状况,而这些例外中包含业务用例的例外,也包含技术上的例外。对于业务用例的例外咱们别无它法,必需要求实施人员与用户共同提供合理的解决方案;而技术上的例外,则必须由咱们码农们手刃之,而这也是我想记录的内容。
我打算分红《前端魔法堂——异常不只仅是try/catch》和《前端魔法堂——调用栈,异常实例中的宝藏》两篇分别叙述内置/自定义异常类,捕获运行时异常/语法异常/网络请求异常/PromiseRejection事件,什么是调用栈和如何获取调用栈的相关信息。
是否是未出发就已经很期待呢?好吧,你们捉紧扶手,老司机要开车了^_^javascript
本篇将叙述以下内容:html
try/catch
就够了。window.onerror
,真的万能吗? 在学习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
这个祖先类型了,其余全部的内置异常类和自定义类都必须继承它。而它的标准属性和方法就如下这寥寥几个而已java
@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的构造函数签名是这样的promise
@constructor @param {String=} message - 设置message属性 @param {String=} fileName - 设置fileName属性 @param {number=} lineNumber - 设置lineNUmber属性
如今咱们看看具体有哪些内置的异常类型吧!浏览器
eval()
时发生的异常,已被废弃只用于向后兼容而已Array
,Number.toExponential
,Number.toFixed
和Number.toPrecision
时入参非法时。null.f()
也报这个错decodeURIComponent('%')
,即decodeURIComponent
,decodeURI
,encodeURIComponent
,encodeURI
关于在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实现以下app
(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."同步代码"就是说没法获取如setTimeout
、Promise
等异步代码的异常,也就是说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~)
Script Error
。若要获得正确的错误信息,则要配置跨域资源共享CORS才能够。window.onerror
实际上采用的事件冒泡的机制捕获异常,而且在冒泡(bubble)阶段时才触发,所以像网络请求异常这些不会冒泡的异常是没法捕获的。window.onerror
也是无能为力。经过Promise来处理复杂的异步流程控制让咱们驾轻就熟,但假若其中出现异常或Promise实例状态变为rejected时,会是怎样一个情况,咱们又能够如何处理呢?
Promise实例的初始化状态是pending,而发生异常时则为rejected,而致使状态从pending转变为rejected的操做有
Promise.reject
类方法reject
方法// 方式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
若在异常发生前咱们没有调用catch
方法来捕获异常,仍是能够经过window
的unhandledrejection
事件捕获异常的
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)
另外,还能够经过window
的rejectionhandled
事件监听异步注册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才会触发该事件。
也许咱们都遇到<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