javascript语言是一门函数式编程的语言,回调函数的使用是常用的,特别是在异步编程时,回调函数会在异步函数完成之后调用。 但是在编程中会遇到这样一类问题,A任务完成之后 才可以开始B任务,B任务完成之后才可以执行C任务,C任务完成后,才能执行D任务。假设ABCD四个任务都是同步的,则应该有下面代码:
task("A").done();
task("B").done();
task("C").done();
task("D").done();
假设ABCD四个任务不全是同步的,最极端的情况,四个都是异步的,则应该有下面回调嵌套式的代码:
task("A").done(function(){
task("B").done(function(){
task("C").done(function(){
task("D").done();
});
});
});
但是这样的有个比较明显的问题,假设不是四个任务,而是10个8个,按照上面的写法,肯定会横向拉得很长。再加上可能会有其他逻辑添加到回调中去,对整个代码的维护是极为不利的。
那么有什么比较好的方式,可以解决这个问题。其实对于函数式编程语言,很早就有一种名为promis的设计模式,就是专门用来解决回调函数嵌套问题的。如果使用promis模式,上述嵌套代码可以写成如下链式结构:
Deffered(task("A"))
.then(task("B"))
.then(task("C"))
.then(task("D"));
这样即便有再多的任务,都可以竖着排下去,逻辑上也可以解套,代码上也简单明了,比之上面的嵌套模式,要好太多。
下面用一个比较活泼的例子,来实现以下promis模式。
有这样一个问题:
程序员小王想娶女神小米。
小米说必须先让她爸爸认可,然后让妈妈认可,然后让大伯认可,且让妈妈认可的前提是爸爸认可,让大伯认可的前提是爸爸妈妈都认可,然后我在考虑~
假设:他们各个人同意的可能性都为80%,而且都必须有一段时间考虑,每个人可以请求两次。
问:小王能不能取到小米~?
使用程序实现以上问题。
按照面向对象的思维,分析以上问题,小王对每个人得请求方式都是一样的,所以可以把这个过程封装到一个Request类中。
这个类至少有以下方法:
aks: 问是否可以娶小米
answer: 答 可以 或者 不可以
fail : 如果请求失败了,就执行此方法
done: 如果最终请求成功了,就执行此方法
begin: 开始执行请求方法
下面是我对这个类的实现:
(function(win){
function Request(name){
if(!(this instanceof Request)){
return new Request(name);
}else {
var p = Promis.create(this);
this.name = name;
this.promis = p;
}
}
Request.prototype={
promis:null,
name:””,
_isfirst:true,
begin:function(){
this.ask();
this.answer();
},
fail:function(){
alert(“求婚失败”);
},
done:function(){
alert(“求婚成功”);
},
ask:function(){
var name = this.name;
var askany = ‘小王:’+name+’,我可以和小米在一起么?’;
this._say(askany);
},
answer:function(){
var name = this.name;
var answerAny = name+”:我得想一下。。。”;
this._say(answerAny);
_this = this;
setTimeout(function(){
if(_this._isAgree()){
_this._say(name+’:好吧我同意了~’);
_this.promis.resolve();
}else{
_this._say(name+’:我不同意’);
if(!_this._isfirst){
_this.promis.reject();
return;
}
_this._isfirst=false;
_this.begin();
}
},1000);
},
_isAgree:function(){
var randomNum = parseInt(Math.random()*5);
if(randomNum>=3){
return false;
}
return true;
},
_say:function(any){
$(“#logFrame”).append(‘<p>’+any+’</p>’);
}
}
win.Request = Request;
})(window);
此时对单个人得请求就可以描述为以下方式:
Request("爸爸").begin();
可以发现在Request类中,有一个Promis类,这个Promis主要的作用是用来承接多个Request之间的状态流转。
promis类至少应该包含如下方法:
then:设置下一个任务
resolve:当前任务完成后,激活下一个任务
reject :当前任务失败后,调用当前任务的fail方法
我对Promis的封装如下:
(function(win){
function Promis(){
}
Promis.prototype = {
task:null,
nextTask:null,
init:function(task){
this.task = task;
},
then:function(task){
this.nextTask = task;
return task.promis;
},
resolve:function(){
if(this.nextTask)
this.nextTask.begin();
else
this.task.done();
},
reject:function(){
this.task.fail();
}
}
win.Promis = Promis;
Promis.create=function(task){
var p = new Promis();
p.init(task);
return p;
}
win.Deferred = function(task){
task.begin();
return task.promis;
}
})(window);
如上,代码中有一个Deferred方法,用来激活首个任务。将Request和Promis两者组合在一起后,就可以使用链式写法完成以上需求,代码如下:
$(document).ready(function () {
$(“#beginBtn”).on(“click”,function(){
$(“#logFrame”).html(“”)
.append(‘<p>程序员小王想娶女神小米。</p>’);
Deferred(Request(“爸爸”))
.then(Request(“妈妈”))
.then(Request(“大伯”))
.then(Request(“小米”));
});
});
最后附上html代码:
<!DOCTYPE html>
<html lang=“en”>
<head>
<meta charset=“UTF-8”>
<title>promis 实验</title>
<script src=“http://gagalulu.wang/blog/js/jquery.js”>
</script>
<script src=“src/js/promis.js”></script>
<script src=“src/js/request.js”></script>
<script src=“src/js/index.js”></script>
<style>
*{
margin: 0;
padding: 0;
}
body{
background:#333;
}
.main{
width:100%;
color:#fff;
}
.main .desc{
width:90%;
margin:25px auto;
padding: 10px;
border: 1px solid #3f3f3f;
}
.main .ctrl{
width:90%;
margin: 20px auto;
}
.main .ctrl .btn{
color: #fff;
border: 1px solid #3f3f3f;
padding: 10px;
border-radius:5px;
cursor: pointer;
}
.main .log{
width:90%;
margin: 20px auto;
min-height:300px;
border: 1px solid #3f3f3f;
padding: 10px;
}
</style>
</head>
<body>
<div class=“main”>
<div class=“desc”>
<p>程序员小王想娶女神小米。</p>
<p>小米说必须先让她爸爸认可,然后让妈妈认可,然后让大伯认可,且让妈妈认可的前提是爸爸认可,让大伯认可的前提是爸爸妈妈都认可,然后我在考虑~</p>
<p>假设:他们各个人同意的可能性都为80%,而且都必须有一段时间考虑,每个人可以请求两次。</p>
<p>问:小王能不能取到小米~?</p>
</div>
<div class=“ctrl”>
<span class=“btn” id=“beginBtn”>开始求婚</span>
</div>
<div class=“log” id=“logFrame”>
<p>程序员小王想娶女神小米。</p>
</div>
</div>
</body>
</html>
对应的代码已经提交到github,大家可以fork观赏,有任何问题,欢迎在下面留言。
github地址:git@github.com:gagaprince/promis.git
转载请注明出处:http://gagalulu.wang/blog/detail/20 您的支持是我最大的动力!