js构建ui的统一异常处理方案(二)

上一篇文章,我分析了同步代码做异常处理是基于责任链模式,而通过try、catch等语句可以很容易地实现这种责任链模式。但是如果是异步调用,我们无法直接通过try、catch语句实现责任链模式,并且通过一个demo证明使用回调函数的方式去实现去实现异常处理的责任链模式是非常繁琐而且代码难以规范的,适用性不高。有没有什么方式能够使得异步js的责任链模式能够更加简单地实现呢?

对于这个问题,我们还是先回到js异步调用上,随着node和npm的普及,js异步调用也越来越被大家重视,于是乎npm引用了一个叫promise的设计模式,立刻引起轰动效应,一时间各个js类库纷纷引入这个设计模式,代替原有的js回调模式。promise后来也被官方接受,成了es6引入的新语法糖之一。因为promise是基于设计模式的,所以promise是可以在任何浏览器上实现出来的,因此很多的框架都有promise的影子,比如jq的$.deferred、angular的promise,另外在ext、dojo里也有对这个模式的封装,可以说目前流行的各大框架,都有promise的实现,由此可见我们没有理由不采用promise来封装我们异步调用。

一、promise的于异步调用

还是先来看看promise,promise对象来自对promise/A+规范的实现,而这个规范本身就可以看成是对一种异步编程的设计模式。关于promise不想在这里涉及太多,因为早就有很多人将他将了多少遍了,这里给出一个实现上一篇文章的例子代码便可。

function fa(){
    return new Promise(function (resolve, reject) {
        setTimeout(function(){
            //执行异步方法a的内容
            resolve();
        })
    }).then(function(){
        //调用异步方法b
        return fb();
    })
}
function fb(){
    return new Promise(function (resolve, reject) {
        setTimeout(function(){
            //执行异步方法b的内容
            resolve();
        })
    }).then(function(){
        //调用异步方法c
        return fc();
    })
}
function fc(){
    return new Promise(function (resolve, reject) {
        setTimeout(function(){
            //执行异步方法c的内容
            resolve();
        })
    })
}
fa();

其中,每个异步过程可以简写为

function fa(){
    return new Promise(function (resolve, reject) {
        setTimeout(function(){
            //执行异步方法a的内容
            //调用异步方法b
            resolve(fb());
        })
    })
}

二、promise的于异常处理

参考promise/A+规范(https://promisesaplus.com/),从2.2.7开始看,有:

promise2 = promise1.then(onFulfilled, onRejected)

这里的onFulfilled和onRejected分别对应promise的两个结果状态fulfilled、rejected,也就是回调。这个onFulfilled指的是成功状态的调用函数,而这个onRejected指的是错误状态的回调。同时then函数的返回值也是一个promise,我们把它称为promise2,于是有:

    • If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure[[Resolve]](promise2, x).
    • If either onFulfilled or onRejected throws an exception epromise2 must be rejected with eas the reason.
    • If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.
    • If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.

从第一条可以看出,如果promise1是rejected态的,但是onRejected返回了一个值(包括undifined),那么promise2还是fulfilled态的,这个过程相当于catch到异常,并将它处理掉,所以不需要向上抛出。

从第二条可以看出,如果promise1本身是rejected态的,或者promise1是fulfilled态但是onFulfilled和onRejected出现了异常,promise2也会是rejected态的,并且会获得promise1的被拒绝的原因,或者是异常。

从第四条可以看出,如果promise1是rejected态的,并且没有定义onRejected,则promise2也会是rejected态的。

还记得我们曾经说过

function f(){
    try{
    } catch(e){
        throw e;
    }
}

//等效于
function f(){}

对promise是一样的,如果的rejected态的,并且未定义onRejected,则被拒绝的原因或异常会继续向上抛出,抛给promise2,这不就是我们想要的异常处理的责任链模式的语法糖吗?即有

return new Promise(function (resolve, reject) {
    resolve();
}).then(function(){
    throw new Error()
}).then(null,function(err){
    //什么也不做
    throw err;
})
//等效于
return new Promise(function (resolve, reject) {
    resolve();
}).then(function(){
    throw new Error()
})

结合之前的a、b、c的三个异步过程的调用,我们可以完善代码为。

function fa(){
    return new Promise(function (resolve, reject) {
        setTimeout(function(){
            //执行异步方法a的内容
            resolve();
        })
    }).then(function(){
        //调用异步方法b
        return fb();
    }).catch(function(e){
        if(e == "方法a能处理的异常"){
            console.log("方法a处理了异常")
        } else {
            console.log("方法a无法处理此异常,继续向上抛出")
            throw e;
        }
    })
}
function fb(){
    return new Promise(function (resolve, reject) {
        setTimeout(function(){
            //执行异步方法b的内容
            resolve();
        })
    }).then(function(){
        //调用异步方法c
        return fc();
    }).catch(function(e){
        if(e == "方法b能处理的异常"){
            console.log("方法b处理了异常")
        } else {
            console.log("方法b无法处理此异常,继续向上抛出")
            throw e;
        }
    })
}
function fc(){
    return new Promise(function (resolve, reject) {
        setTimeout(function(){
            resolve();
        })
    }).then(function(){
        throw "方法acb都不能处理此异常";
        //throw "方法a能处理的异常";
        //throw "方法b能处理的异常";
        //throw "方法c能处理的异常";
    }).catch(function(e){
        if(e == "方法c能处理的异常"){
            console.log("方法c处理了异常")
        } else {
            console.log("方法c无法处理此异常,继续向上抛出")
            throw e;
        }
    })
}
fa().catch(function(e){
    console.log("最顶层处理了此异常");
});

这里的catch就相当于then(null,onRejected)。

当然如果a、b、c都没有可以处理的异常,则上面的代码就简化为第一节中的代码。

从测试结果可以看出,promise的异常处理也遵循责任链模式。因此用这种模式调用异步过程,异常的处理方式是类似于传统过程式调用的异常的处理方式的。同时这种调用方式是E6规范中的,是js未来的发展方向,以此规范封装js的异步回调,是适应js发展方向的。

总结

我们通过引入promise这种异步调用模型,解决了异步调用不能使用责任链模式的问题。除了异步过程的js的函数调用情况,如函数柯里化、惰性函数都适用于传统try、catch语法,这里不再演示。

将异步过程调用过程解决后,那么基本上js的函数调用的异常处理都可以适用于责任链模式。既然技术上没有问题了,那么下一章将分享我们统一异常处理的设计方案。

时间: 2024-12-14 18:05:59

js构建ui的统一异常处理方案(二)的相关文章

js构建ui的统一异常处理方案(三)

笔者之前分析了如何实现js的责任链异常处理的方法,通过promise这个异步模型,我们能够对同步方法和异步方法的两种情况,均可以实现责任链模式.有了这些武器,我们就可以开始设计ui的统一异常处理方案了. 1.统一异常处理方案 这里所谓统一异常处理方案,其实就是指对那些底层无法处理的,一层层抛到了边界类的异常,在边界类中根据异常的不同类型,做出不同处理方案的处理策略.为了能在边界类中对异常类型做出判断,我们需要将常用的异常类型定义出来,再将原始异常包装为这些系统内部定义的异常类型.所以,整个统一处

项目总结(二、统一异常处理)

二.统一异常处理 我们知道项目如果采用分层结构的话,异常需要一层层往上抛,直至到action层,然后在action处理异常,提示友好的异常信息给用户.如果在action的每个方法中都用try{}catch处理,那么类似的代码会很多,且如果需要在异常处理时新增一些逻辑,则需要更改的地方很多,工作量大,不易于维护.于是项目打算采用struts的拦截器机制,实现统一异常处理. 1.struts配置文件 首先自定义拦截器栈,配置如下: <!-- 自定义拦截器栈 --> <interceptors

Android进阶——构建UI布局的多种方式总结

引言 作为Android App,给人第一印象的就是用户界面(UI),简洁友好的UI,自然会给用户优秀的体验,自然很容易就得到用户的认可和赞许,这样App才变得真正的有价值.所以作为开发App的第一步,UI尤为重要,构建UI有很多种方式:xml静态布局.java动态代码.HTML构建(借助WebView)和第三方开源框架等. 一.构成UI的基本元素--View和ViewGroup概述 在Android中绝大部分的UI组件都是存放在android.widget包及其子包.android.view包

Android入门——构建UI布局的多种方式

引言 作为Android App,给人第一印象的就是用户界面(UI),简洁友好的UI,自然会给用户优秀的体验,自然很容易就得到用户的认可和赞许,这样App才变得真正的有价值.所以作为开发App的第一步,UI尤为重要,构建UI有很多种方式:xml静态布局.java动态代码.HTML构建(借助WebView)和第三方开源框架等. 一.构成UI的基本元素--View和ViewGroup概述 在Android中绝大部分的UI组件都是存放在android.widget包及其子包.android.view包

error.jsp错误页面跳转,统一异常处理

常见web项目中会用倒计时然后跳转页面来处理异常 error.jsp关键代码: <script language="javascript" type="text/javascript"> var timer; //启动跳转的定时器 function startTimes() { timer = window.setInterval(showSecondes,1000); } var i = 5; function showSecondes() { if

springboot + shiro 权限注解、请求乱码解决、统一异常处理

springboot + shiro 权限注解.请求乱码解决.统一异常处理 前篇 后台权限管理系统 相关: spring boot + mybatis + layui + shiro后台权限管理系统 springboot + shiro之登录人数限制.登录判断重定向.session时间设置 springboot + shiro 动态更新用户信息 基于前篇,新增功能: 新增shiro权限注解: 请求乱码问题解决: 统一异常处理. 源码已集成到项目中: github源码: https://githu

Spring Boot2从入门到实战:统一异常处理

都说管理的精髓就是“制度管人,流程管事”.而所谓流程,就是对一些日常工作环节.方式方法.次序等进行标准化.规范化.且不论精不精髓,在技术团队中,对一些通用场景,统一规范是必要的,只有步调一致,才能高效向前.如前后端交互协议,如本文探讨的异常处理. 1. Spring Mvc中的异常处理 在spring mvc中,跟异常处理的相关类大致如下 上图中,spring mvc中处理异常的类(包括在请求映射时与请求处理过程中抛出的异常),都是 HandlerExceptionResolver 接口的实现,

前端 页面无刷新方案二

开始方案二   用html5     history.pushState(state,null,url);  (IE9及以下版本不支持) /** * HTML5 history and ajax */ $(function(){ var ajax, currentState; $('li a').unbind().bind('click',function(e){ e.preventDefault(); var target = e.target, url = $(target).attr('h

JS基础知识回顾:引用类型(二)

ECMAScript中的Date类型是在早期Java中的java.util.Date类基础上构建的. 因此,Date类型使用自UTC(Coordinated Universal Time,国际协调时间)1970年1月1日午夜零点开始经过的毫秒数来保存日期. 在使用这种数据存储格式的条件下,Date类型保存的日期能够精确到1970年1月1日或之后的285616年. 要创建一个日期对象,使用new操作符和Date构造函数即可:var now=new Date(); 在调用Date构造函数而不传递参数