jquery Deferred使用经验
这周做了个小活动(http://aoqi.100bt.com/zt-2016duanzi/index.html),刚开始时候没看好需求,逻辑都写一块了
最后各种坑要填补,从中也获取了些经验和教训,下面说说这里会用到的$.Deferred;
关于jquery里面的deferred的基本使用方法,阮一峰大婶已经有文章说明了,链接如下:
http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html
这里就说说里面没提及的吧:
页面中逻辑比较麻烦的就是获奖名单的渲染问题,如下图
这里需要的逻辑是:
1、获取时间,判断是否有活动过了投票时间,没有则不可点击,页面不渲染
2、过来时间后,判断能否获取到获奖名单,获取到就渲染次阶段的页面,获取不到则渲染前一个阶段的页面,
前一个阶段的页面还是获取不到则往前回溯,如果到第一个阶段还是没有则页面不渲染。
假设四个页面对应的获奖名单是1.html、2.html、3.html、4.html;
假如此时有时间判断活动时间已经在第四阶段,即是前三个阶段都结束,我们的实现逻辑或许是
$.get('3.html').done(function(){ //渲染页面 }).fail(function (argument) { $.get('2.html').done(function (argument) { //渲染页面 }).fail(function (argument) { function (argument) { $.get('1.html').done(function (argument) { // 渲染页面 }).fail(function (argument) { // 不渲染页面 }) } }) })
这是很不可取的,在每个不同的阶段都要嵌套一次,而且每个请求都要等上一个请求发完才发,太慢了。。。
于是就改成类似下面的
var linkArr = ['1.html','2.html','3.html','4.html'] $nav.each(function(index, el) { var $self = $(this); $.get(index+'.html').done(function (argument) { $self.text('获奖名单出来了') }).fail(function (argument) { $self.addClass('graynav').text('敬请期待'); }); });
能“并行”地发出多个请求,看似不错,然后在添加点击渲染事件,点击不同的nav渲染已经出来的相应获奖名单,嗯,这也是逻辑上需要的
$('.aCommon_nav').click(function(event) { if($(this).hasClass('graynav')){ return false; } var i = $('aCommon_nav').index($(this)); $.get(linkarr[i], function(data) { $inforWrap.html(data) }); });
这时候,我们只需在“并发”请求结束后调用最后一个可点的nav就完成了。。真赞
可是安装现在这种写法,我们并不能判断是否已经完成了请求,或许需要维护个全局变量num,在每次fail和done再加1,当num等于需要发送的个数之后再调用函数
var linkArr = ['1.html','2.html','3.html','4.html'] var i = 0; $nav.each(function(index, el) { var $self = $(this); if($self.attr('xxx')<=timenum){//由时间判断出的是已经结束的标志 $.get(index+'.html').done(function (argument) { i++; $self.text('获奖名单出来了'); if(i==LEN){ //DoClickEvent() } }).fail(function (argument) { i++; $self.addClass('graynav').text('敬请期待'); if(i==LEN){ //DoClickEvent() } }); } });
此时就已经基本完成了此次逻辑了,但是代码实在太糙,需要改进下,下面就用when来完成吧~
var linkArr = ['1.html','2.html','3.html','4.html'], deferredArr = []; $('.aCommon_nav').each(function(index, el) { var $self = $(this); if($self.attr('xxx')<=timenum){//由时间判断出的是已经结束的标志 deferredArr.push($.get(index+'.html').done(function (argument) { $self.text('获奖名单出来了'); }).fail(function (argument) { $self.addClass('graynav').text('敬请期待'); })) } }); $.when.apply(null,deferredArr).always(function(arg){ $('.aCommon_nav:not(.garynav)').last().click(); })
这时候,貌似完成了这个逻辑,but,调试之后发现有时候先执行完done,然后fail然后always然后又done,貌似这顺序有些乱来了。。。
然而我们希望的是先done或者fail最后执行always。
发现原因是绑定顺序导致的,于是发现了两条路走,
第一条,把done和fail逻辑都写在always里面,如下
$.when.apply(null,defferredArr).always(function(arg){ $.each(arguments, function(index, val) { val.done(function (argument) { $self.text('获奖名单出来了'); }).fail(function (argument) { $self.addClass('graynav').text('敬请期待'); }) }); $('.aCommon_nav:not(.garynav)').last().click(); })
这感觉好赞,deferred的回调函数都写一块了,维护起来也开心。
但是当deferredArr里面只有一个元素的时候,发现报错了,好尴尬,只能断点看看,
发现always回调函数里面的arguments竟然是一个数组,第一项是请求返回的数据,第二个是返回状态,第三个是此次请求的deferred对象,
于是我们要加个判断,或加个try.catch包裹着done和fail,代码就不贴了,是在太糊弄了。
第二条路,使用setTimeout的黑魔法
var linkArr = ['1.html','2.html','3.html','4.html'], deferredArr = []; $('.aCommon_nav').each(function(index, el) { var $self = $(this); if($self.attr('xxx')<=timenum){//由时间判断出的是已经结束的标志 deferredArr.push($.get(index+'.html').done(function (argument) { $self.text('获奖名单出来了'); }).fail(function (argument) { $self.addClass('graynav').text('敬请期待'); })) } }); $.when.apply(null,deferredArr).always(function(arg){ setTimeout(function (argument) { $('.aCommon_nav:not(.garynav)').last().click(); },0) })
于是这个业务也完成了,废话也讲完了