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