文章

严肃活泼,团结紧张。战天斗地,为民为邦。

引言

实践是检验真理的唯一标准

在相当长的一段时间内,对于Promise和async,我会用,但是仅仅限于实现一个功能,而没有深究它的表现,
比如我们换种写法它会不会有不同的表现,是不是有更好的写法来应对我当前的业务场景?
所以在今天,我将回归初心对这些方面进行探索,介绍下职责链模式,因为我感觉,在我现在遇到的场景下可以套用这种编程范式。
之后我将对我的感觉进行验证。

从回调的N种写法说起

作为一个前端,对Ajax不可谓不熟,我刚工作那会甚至有种说法,你是3k的切图崽还是5k的切图崽只取决于你是否了解Ajax。
不得不说通过Ajax以Json数据的形式来进行前后端交互在真正意义上将前端分离成一个职位。
众所周知Js是单线程异步的,在这里回调就应用而生。
xhr的写法我已记不太清,我们就以jQuery来举例。

$.ajax{
    url:'...',
    date:'...',
    type:'post',
    success:function(value){
        ....
        //这里就是回调
    }
}

这是一个非常常见的场景,假如,我们要先去地址A请求,获取到数据再去地址B请求得到数据,再去地址C请求得到一个数据....
最后在执行业务逻辑会变成什么样呢?

$.ajax{
    url:'...A',
    date:'...',
    type:'post',
    success:function(value){
        ....
        //这里就是回调
        $.ajax{
            url:'...B',
            date:'...',
            type:'post',
            success:function(value){
                ....
                //这里就是回调
                        $.ajax{
                        url:'...C',
                        date:'...',
                        type:'post',
                        success:function(value){
                            ....
                            //这里就是回调
                        }
                    }
            }
        }
    }
}

回调地狱就这么诞生了..也许你可以封装一下,让它好看一点,然而这样的代码无论是读起来还是改起来依旧是让人感觉身处地狱。
幸运的是我工作的时候jQuery已经是2.X了,它的AJAX实现了Promise。所以,实际中我不必去面对这种代码。

var promise = $.ajax{
    url:'...A',
    date:'...',
}
promise.then(function(value){
    ....
    return $.ajax{
        url:'...B',
        date:'...'
    }
});
promise.then(function(value){
    ...
    return $.ajax{
        url:'...C',
        date:'...'
    }
})
promise.then(function(value){
    ....
})

虽然看起来不怎么优雅但是比回调地狱还是强太多了,不是么。
我不太清楚jQuery实现的promise有没有race方法,现在也并无动力去验证。
之后出现了一种,generator yeild 的写法。我并没有应用于实践中过,它看起来是这个样子的...

function* job(){
    yeild 某种异步操作..
    yeild 某种异步操作...
     .....
}

使用yeild的话,那个异步操作会相当于被转换为同步的,停留在那等待异步返回才会去执行下面的操作,个人感觉结合Promise会更好用。
虽然我没用过,接下来就是async和await。
与其说它是新的语法,不如说是generator yeild的语法糖,它看起来通常是这样的。

var job = async(){
    await 某种异步操作..
    await 某种异步操作..
    ...
}

几乎和上面如出一辙,换了个写法罢了,在实践中我几乎是结合着Promise来用。
至此,从使用上来说,已并无大碍,基本写法就是这么简单,但是人人都止步于此的话,谁来开发那些好用的框架呢?
所以下面让我稍微哦深入学习一下。

Promise

从这里开始,默认是ES6的实现。
简单来说Promise就是一个可以处理异步操作的对象,提供操作这个异步操作的API,通常会保存这个异步操作的状态,同时状态的改变不可逆。
ES6中Promise是一个构造函数,通常的用法:

var pormise = new Promise((resolve,reject)=>{
    //做一些异步操作如果成功
    resolve();
    //如果失败 则调用reject
})
//resolve会使这个promise对象状态变成成功结束。
//reject则使这个promise对象状态变成失败结束。

rejcet 的话会返回一个新的promise对象,在这里你可以做一些弥补。
掌握这些,当你需要进行异步操作的时候,这些就已经足够了,但是如果你想写出健壮的代码还需要掌握。

var pormise = new Promise((resolve,reject)=>{
    //做一些异步操作如果成功
    resolve();
    //如果失败 则调用reject
})

pormise.then((value)=>{
   //对这个数据作一些事情
   
})
promise.catch((err)=>{
    //错误处理
})
promise.finally(()=>{
    //无论状态如何都会执行
    //文档中说似乎无法通过参数获得状态,我觉得,在这里写些作为错误处理的兜底的代码应该可行。
    //值得一提的是,Jquery有done,谷歌浏览器也有实现这个方法,但是node至少再8.X还没有实现这个方法,虽然最新已经是10.X了..
})

到这里,如果处理得当你的promise已经足够健壮,不会因为一些小风浪而翻船。
假如你想更加优雅的处理回调地狱,你还需要了解。

//Promise.all
const p = Promise.all([p1, p2, p3]);
//它会把多个promise包装成一个大promise,它们是同时执行的,只有在都结束的时候,才会调用p的then
//Pomise.race
const p = Promise.race([p1, p2, p3]);
//和上面的区别是这里是顺序执行的。
//需要注意的是,上面两个方法都是执行一组promise,如果有一个promise为reject状态的话,那么它们整体,也就是说这个p也将是reject状态。

至此无论是并发,还是链式调用都并无大碍了,剩下的就是在实践中去应用了,平心而论它们看起来还是蛮简单的。

async+await

同步与等待,这主要解决的是异步的异步问题,异步照理说是个优点,但是在一些场景我们往往不希望它返回的那么快,
async await便应用而生。

//基本用法就像之前说的
async function job(){
    await p1
    await p2 
    await p3
    ...
}
//这里Job也会是一个Promise对象

基本可以看做generator的语法糖,用Promise.race也能做到类似的用法,问题是race不怎么好阻塞,而且明显async的写法要更好处理一些。

//稍微需要注意下错误处理
async function job(){
    try{
        await p1
        await p2 
        await p3 
    }.catch(err){
        ....
    }

    ...
}
job.then(()=>{
    
});
job.catch(()=>{

})
//p1,p2,p3一起做错误处理,之后job自身也可以错误处理。
//不过我更推荐分开进行,直接使用promise的catch方法。
//需要并发的话,在内部还能使用Promise.all

不得不说,async await结合promise能用更简洁优雅的代码玩出更多的花样,这对我之后重构代码将有很大的帮助。

职责链模式

定义:使多个对象都有机会处理请求,从而避免请求的发送者和对象之间的耦合关系。
什么意思呢?就是说将多个对象连成链条,然后传一个请求进来,总有一个能解决它。
直白来说就是使用多态来重构的switch case。

//随便举个列子
//假设一个购买活动,有几种购买可能。
//比如:预付200得到优惠券50 预购300得到100优惠券 预付100没有优惠 
//通常来说一个巨大的sitch case可以解决这个问题,问题是这违反了开放-封闭原则,不利于扩展。

var order200=(pay)=>{
    if(pay===200){
        console.log('预付200,得到50优惠券')
    }else{
        return 'nextSuccessor' //像后传递。
    }
}
var order300=(pay)=>{
    if(pay===300){
        console.log('预付300,得到100优惠券')
    }else{
        return 'nextSuccessor' //像后传递。
    }
}
var order100=(pay)=>{
    if(pay===100){
        console.log('预付100,无优惠券')
    }else{
        return 'nextSuccessor' //像后传递。
    }
}

var Chain =function (fn){
    this.fn=fn;
    this.successor=null
}

Chain.prototype.setNextSuccessor =function (successor){
    return this.successor = successor;
}
Chain.prototype.passRequest  = function (){
    var ret = this.fn.apply(this,arguments);
    if(ret === 'nextSuccessor'){
        return this.successor && this.successor.passRequest.apply(this.successor,arguments);
    }
    return ret;
}
//包装节点
var cOrder200=new Chain(order200);
var cOrder300=new Chain(order300);
var cOrder100=new Chain(order100);
//指定顺序
cOrder100.setNextSuccessor(cOrder200);
cOrder200.setNextSuccessor(cOrder300);
//调用
cOrder100.passRequest(100);
cOrder100.passRequest(200);
cOrder100.passRequest(300);

这段代码有种为了举例而举例,颇有杀鸡用牛刀的意思,不过很好懂。
值得一提的是,在这里踩了两个新语法糖。
因为作用域的关系箭头函数不能作为构造函数来使,也不适合用来挂在prototype上。
这么看来为解决自调问题产生的箭头函数,设计的很精巧,它能且只能解决自调问题。

结语

emmmm..到最后我发现Promise和async await 虽然结合密切,但是与后面的职责链似乎并不关联。
我想处理的场景也是更类似链式回调而不是职责链。
假如在职责链里加入一个手动的Next方法,我也能处理异步问题.....
这里似乎又可以引入命令模式。
所谓学的越多不会的越多大概就是这么一回事吧。
我现在除了代码重构的规则,又多了个命令模式想要探究。
只是这篇笔记已经够长了,这么看来明天学什么也有着落了。
按照这个进度大概周末我可以开始着手重构之前的爬虫代码,岂不是正好?

评论

This is just a placeholder img.