美文网首页
不那么随处可见的JavaScript学习笔记-漫谈Promise

不那么随处可见的JavaScript学习笔记-漫谈Promise

作者: zephryu | 来源:发表于2018-05-31 10:20 被阅读16次

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

    题图--引自网络,侵删,请联系我

    引言

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

    在相当长的一段时间内,对于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(()=>{
        //无论状态如何都会执行
        //文档中说似乎无法通过参数获得状态,我觉得,在这里写些作为错误处理的兜底的代码应该可行。
    })
    

    到这里,如果处理得当你的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方法,我也能处理异步问题.....
    这里似乎又可以引入命令模式。
    所谓学的越多不会的越多大概就是这么一回事吧。
    我现在除了代码重构的规则,又多了个命令模式想要探究。
    只是这篇笔记已经够长了,这么看来明天学什么也有着落了。
    按照这个进度大概周末我可以开始着手重构之前的爬虫代码,岂不是正好?

    相关文章

      网友评论

          本文标题:不那么随处可见的JavaScript学习笔记-漫谈Promise

          本文链接:https://www.haomeiwen.com/subject/qaafsftx.html