上一篇文章,我分析了同步代码做异常处理是基于责任链模式,而通过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+规范(),从2.2.7开始看,有:
promise2 = promise1.then(onFulfilled, onRejected)
这里的onFulfilled和onRejected分别对应promise的两个结果状态fulfilled、rejected,也就是回调。这个onFulfilled指的是成功状态的调用函数,而这个onRejected指的是错误状态的回调。同时then函数的返回值也是一个promise,我们把它称为promise2,于是有:
-
- If either
onFulfilled
oronRejected
returns a valuex
, run the Promise Resolution Procedure[[Resolve]](promise2, x)
. - If either
onFulfilled
oronRejected
throws an exceptione
,promise2
must be rejected withe
as the reason. - If
onFulfilled
is not a function andpromise1
is fulfilled,promise2
must be fulfilled with the same value aspromise1
. - If
onRejected
is not a function andpromise1
is rejected,promise2
must be rejected with the same reason aspromise1
.
- If either
从第一条可以看出,如果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的函数调用的异常处理都可以适用于责任链模式。既然技术上没有问题了,那么下一章将分享我们统一异常处理的设计方案。