从早期从事基于java的服务器端开发,再到之后从事基于web和js的ui开发,总体感觉基于web页面的ui开发远不如服务器端健壮。主要是早期ie浏览器功能太弱小,很多业务*放到服务器端去实现,浏览器端技术设计的比较简单。其次,js这门语言对于异常里也不够完善,没有一套足够完善的异常处理思路,尽管js存在throw、try、catch这种异常的语法,但是js本身是一门函数式编程的语言,try、catch在很多回调场景中是那么无力。
所以一直以来我就想构建出一个完善的js异常处理机制,而且还要基于具体ui实际需求,做出具体的处理逻辑。简单来说,当boss问我们做这种统一的异常处理有什么好处时,作为前端工程师,最理想的回答就是增加用户体验,作为前段工程师,用户体验是我们最大的价值,因为只有增加用户体验,公司才能留住用户,保持盈利,我们才能发挥出价值。那么如何提升用户体验呢?当用户遇到了不同的错误情况,使用最适合的提示方式提供给用户,不就是增加了用户的使用体验吗?
所以统一的异常处理的目的就是,能够使用简单、严谨的思路让程序员开发出更容易维护的代码,增加前端js代码的健壮性,同时将异常处理的提示具体实现和业务逻辑解耦,由统一的提示接口去将处理异常信息。
那么这我们提出设计方案之前,先来分析一下传统的异常处理方法,以及js中使用时候的问题,也来想想为什么我们平时在js中使用异常处理的情况是那么少。
1.责任链模式的异常处理理论
传统的异常处理理论,就是一个责任链模式,当方法A调用方法B,方法B调用方法C,其中每一个方法其实都可能抛出异常,可能处理异常。
function f(){
try{
} catch(e){
//什么也不做
throw e;
}
} //等效于
function f(){}
本身方法调用就是一个链,而异常处理则处于这个链上。当一个方法抛出一个异常时,他本也是异常的第一个接收人(catch到异常),如果是他职责身份能处理的异常,他就应该立刻处理此异常,不再向上抛出;否则就应该向上抛出,抛到上一层方法。依次类推,直到抛到最顶层。通常这个最顶层是一个用户或者系统调用接口,这个接口作为方法调用的发起者,同时也应该是异常处理的最终响应者,在这一层我们将真正地去处理底层方法无法处理的异常。
如何判断一个异常是一个方法能否处理的呢?依据“最小知道原则”和“职责单一原则”,一个异常如果属于其业务范围内的一部分,就应该处理这个异常,如果需要外界知晓这个异常存在,就应该将这个异常加工后抛到上一层。
function fa(){
try{
fb();
}catch(e){
if(e == "方法a能处理的异常"){
console.log("方法a处理了异常")
} else {
console.log("方法a无法处理此异常,继续向上抛出")
throw e;
}
}
}
function fb(){
try{
fc();
}catch(e){
if(e == "方法b能处理的异常"){
console.log("方法b处理了异常")
} else {
console.log("方法b无法处理此异常,继续向上抛出")
throw e;
}
}
}
function fc(){
try{
throw "方法acb都不能处理此异常";
//throw "方法a能处理的异常";
//throw "方法b能处理的异常";
//throw "方法c能处理的异常";
}catch(e){
if(e == "方法c能处理的异常"){
console.log("方法c处理了异常")
} else {
console.log("方法c无法处理此异常,继续向上抛出")
throw e;
}
}
} (function(){
try{
fa()
}catch(e){
console.log("最顶层处理了此异常");
}
})()
其中a、b、c中如果没有可以处理的异常,try、catch语句是可以省略掉的,这样代码就会简写为
function fa(){
fb();
}
function fb(){
fc();
}
function fc(){
throw "方法acb都不能处理此异常";
} (function(){
try{
fa()
}catch(e){
console.log("最顶层处理了此异常");
}
})()
是不是简单了不少,我们实际开发中更多的是这种例子,因为一个函数出现错误,后续执行都将无法正常进行,所以是需要向上层抛出的。因此js语法中的异常处理策略简化了整个过程。
所以说,传统的异常处理就是一个责任链模式。然而js中真的就可以使用这种责任链模式的异常处理吗?为什么我们在开发js中,很少采用这种责任链模式的异常处理呢?接下来我们继续介绍js的异步调用。
2.js异步调用的异常处理
js是个很神奇的语言,用这个语言你可以像用c语言那样在全局变量里,面向过程地编写你的代码,也可以像使用java那种,面向对象地编写代码,他还可以使用现状渐渐开始火起来在函数式的方法编写代码。
在js里面,函数可以向变量一样被声明,可以做方法的入参,可以做方法的返回值,这些都是js不可或缺的语法特性。
因为js还有没有语法级的阻塞方案,这样你无法同步两个不同的线程能够同步彼此,例如一个异步请求,或者调用。当调用一个异步方法,因为没有语法级的阻塞方式,所以整个调用过程中,你无法顺序地编写调用过程,唯一能做的事情只有回调。
这些语法特性使得我们开发的时候,只能做到基于函数回调情况去处理不同的状况,而责任链模式的异常处理也变得不再适用。
function f(){
throw "error";
} try{
setTimeout(f)
} catch(e){
console.log(e) //接收不到这个error的
}
这种情况在实际开发中会经常遇到。最常见的就是一个ajax的异步请求,除此之外,异步io的api、地理定位的api、摄像机的api,这些都是异步的。再比如我们模拟一个alert方法,不使用系统的alert而是我们自己的alert(系统的alert函数调用后会弹出真正的模式对话框,此时线程挂起,运行阻塞,当用户点掉alert对话框后线程才会唤起)。与系统alert不一样,我们自己做的alert只能在回调里加alert后续的业务方法。
//使用Window对象的alert
alert("我被阻塞了");
console.log("执行完毕"); var myAlert = {
show: function(fn) {
//绘制alert对话框略 //监听用户点击事件,点击后回调fn函数
var body = document.querySelector("body");
var _fn = function() {
//去除对话框略
body.removeEventListener("click", _fn)
fn && fn();
}
body.addEventListener("click", _fn, false);
}
} //调用自定义的alert
myAlert.show(function() {
console.log("执行完毕");
})
这种回调在js程序设计中常常被用到,因为这种方式是不支持责任链模式的,所以try、catch这种异常处理的语法在这种调用中很少会被使用到。那么这种基于回调的调用过程,一般使用什么方法做异常处理呢?通常函数本身会有一个错误的回调入参,参数要求是一个我们还是使用a、b、c这三个方法彼此调用来说明。
function fa(error){
var errorFn = function(e){
if(e == "方法a能处理的异常"){
console.log("方法a处理了异常")
} else {
console.log("方法a无法处理此异常,继续向上抛出")
error && error(e);
}
} setTimeout(function(){
fb(errorFn);
})
}
function fb(error){
var errorFn = function(e){
if(e == "方法b能处理的异常"){
console.log("方法b处理了异常")
} else {
console.log("方法b无法处理此异常,继续向上抛出")
error && error(e);
}
} setTimeout(function(){
fc(errorFn);
})
}
function fc(error){
var errorFn = function(e){
if(e == "方法c能处理的异常"){
console.log("方法c处理了异常")
} else {
console.log("方法c无法处理此异常,继续向上抛出")
error && error(e);
}
} setTimeout(function(){
try{
throw "方法acb都不能处理此异常";
//throw "方法a能处理的异常";
//throw "方法b能处理的异常";
//throw "方法c能处理的异常";
}catch(e){
errorFn(e);
}
})
} fa(function(){
console.log("最顶层处理了此异常");
})
注意这里的errorFn是不可以省略的,因为这个责任链模式是我们手动书写出来的。所以要想实现异步过程的责任链模式,是必须通过自己手动完成的,js并没有提供什么语法糖帮我们简化这个过程(其实也是有的)。
而且,只有最里层的fc当中使用try、catch语句,fb、fa都无法再使用try、catch语句了,因为一旦使用了这种回调方案,就再也无法回归传统的try、catch处理了,这也是try、catch语法无法在js里面流行的一大原因。
这里基本分析出了传统js代码在异常处理方面的方案和出现的问题,如何更好地解决异常处理问题,提供我们程序的健壮性我们值得进一步思考,待续。。。。