在前面,有一篇文章介绍了jQuery的异步对象Deferred,通过他,我们可以实现一些操作比如回调函数在异步操作(耗时)完成之后再执行。比如这样的场景,我们在编辑页面,对一个产品对象进行编辑,而该对象一些属性比如所属渠道,是可以下拉选择,而所属渠道不是固定不变的,而是根据后台接口channel/getAll返回给前台页面。这时候,我们需要先初始加载渠道这个下拉框,然后根据产品自己的属性值的渠道来反显渠道initForm()。这是一个很常见的场景。如果在渠道下拉框没有初始化完成之前,产品渠道先反显了,那么很有可能最后又初始化渠道下拉框的时候,将这个反显的值冲掉了。
让一个操作在另一操作之后执行,我们可以使用嵌套的方式,让一个异步接着另一个异步,这样虽然可以解决问题,但是却造成了另外一个callback hell的问题。代码可读性差。
为此,我们需要用到异步对象来让完成这个操作。
function getAll(){
var dtd = $.Deferred();
$.ajax({
url:"channel/getAll",
success:function(){xxx;dtd.resolve();xxx}
});
return dtd.promise();
}
function initForm(){xxx;}
$.when(getAll()).done(initForm);
这样,就解决了我们的问题,初始化表单会在初始化渠道getAll()方法完成之后进行。
在实际的场景中,还有可能有这样的需求,几个耗时操作,需要按照顺序执行。我们知道$.when()方法,可以运行多个耗时的异步操作[d1,d1,d3],但是这些耗时操作是同时执行的,只有done()里面的方法会在这些异步操作[d1,d2,d3]都执行完成之后,才执行。
function d1(){
var dtd = $.Deferred();
console.log("d1 : -> start");
setTimeout(function(){
console.log("d1 : -> end");
dtd.resolve();
},1000);
return dtd.promise();
}
function d2(){
var dtd = $.Deferred();
console.log("d2 : -> start");
setTimeout(function(){
console.log("d2 : -> end");
dtd.resolve();
},2000);
return dtd.promise();
}
function d3(){
var dtd = $.Deferred();
console.log("d3 : -> start");
setTimeout(function(){
console.log("d3 : -> end");
dtd.resolve();
},3000);
return dtd.promise();
}
console.log("all: -> start");
$.when(d1(),d2(),d3()).done(function(){
console.log("all: -> end");
});
这样执行的结果是:
想要让他们顺序执行,我们需要在每一个函数执行之后调用done(),然后接下来执行下面的操作。
console.log("all: -> start")
d1().done(function(){
d2().done(function(){
d3().done(function(){
console.log("all: -> end");
});
});
});
这样,虽然是按照顺序执行队列,但是还是回到了最初我们提到的callback hell。代码不够优雅。这里可以利用Array的一个reduce方法,让代码可读性更好,而且也能达到我们的预期,异步操作按照顺序执行。这种方式也不再使用done()后面接下一个函数的方式,而是采用then()来构建一个异步队列。reduce(function(){},$.Deferred().resolve())有两个参数,第二个参数是默认值。当我们第一次执行then()时,prev是没有值的,需要传入一个默认的promise对象,就是$.Deferred.resolve()。
function app(){
var tasks = [d1,d2,d3];
return tasks.reduce(function(prev,next){
return prev.then(next);
},$.Deferred().resolve());
}
$.when(app()).done(function(){
console.log("all: -> end");
});
最终的演示效果如下:
这样通过jQuery的Dererred对象,我们成功的构建了一个顺序执行队列。完整示例代码:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>deferred-reduce</title>
<script src="js/jquery/jquery-1.12.4.min.js" type="text/javascript"></script>
</head>
<body>
<script type="text/javascript">
$(function(){
function d1(){
var dtd = $.Deferred();
console.log("d1 : -> start");
setTimeout(function(){
console.log("d1 : -> end");
dtd.resolve();
},1000);
return dtd.promise();
}
function d2(){
var dtd = $.Deferred();
console.log("d2 : -> start");
setTimeout(function(){
console.log("d2 : -> end");
dtd.resolve();
},2000);
return dtd.promise();
}
function d3(){
var dtd = $.Deferred();
console.log("d3 : -> start");
setTimeout(function(){
console.log("d3 : -> end");
dtd.resolve();
},3000);
return dtd.promise();
}
function app(){
var tasks = [d1,d2,d3];
return tasks.reduce(function(prev,next){
return prev.then(next);
},$.Deferred().resolve());
}
console.log("all: -> start")
/*
d1().done(function(){
d2().done(function(){
d3().done(function(){
console.log("all: -> end");
})
})
});*/
$.when(app()).done(function(){
console.log("all: -> end");
})
});
</script>
</body>
</html>