zoukankan      html  css  js  c++  java
  • js构建ui的统一异常处理方案(一)

    从早期从事基于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代码在异常处理方面的方案和出现的问题,如何更好地解决异常处理问题,提供我们程序的健壮性我们值得进一步思考,待续。。。。

  • 相关阅读:
    Jzoj4889 最长公共回文子序列
    Jzoj4889 最长公共回文子序列
    Jzoj4888 最近公共祖先
    Jzoj4888 最近公共祖先
    Jzoj5441【NOIP2017提高A组冲刺11.1】序列
    Jzoj5441【NOIP2017提高A组冲刺11.1】序列
    hdu1269 迷宫城堡
    洛谷P1991 无线通讯网
    左神算法进阶班1_2判断两个树的结构是否相同
    判断两条链表是否相交(公共部分)并找出相交处
  • 原文地址:https://www.cnblogs.com/laden666666/p/5281993.html
Copyright © 2011-2022 走看看