不那么随处可见的JavaScript学习笔记-漫谈Promise&&async&&职责链

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

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

引言

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • 异步编程对JavaScript语言太重要。Javascript语言的执行环境是“单线程”的,如果没有异步编程,根本...
    呼呼哥阅读 7,301评论 5 22
  • Promise的含义:   Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和...
    呼呼哥阅读 2,167评论 0 16
  • Promiese 简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,语法上说,Pr...
    雨飞飞雨阅读 3,352评论 0 19
  • async 函数 含义 ES2017 标准引入了 async 函数,使得异步操作变得更加方便。 async 函数是...
    huilegezai阅读 1,257评论 0 6
  • 弄懂js异步 讲异步之前,我们必须掌握一个基础知识-event-loop。 我们知道JavaScript的一大特点...
    DCbryant阅读 2,707评论 0 5