【jQuery】Deferred(延迟)对象
本文针对jQuery-todolist项目中使用到的Deferred(延迟)对象进行具体分析
$.Deferred() 是一个构造函数,用来返回一个链式实用对象方法来注册多个回调,并且调用回调队列,传递任何同步或异步功能成功或失败的状态。
提示:1. $.Deferred() 构造函数创建一个新的 Deferred(延迟)对象, jQuery.Deferred 可传递一个可选的函数,该函数在构造方法返回之前被调用并传递一个新的 Deferred 对象作为函数的第一个参数。例如被调用的函数可以使用 deferred.then()来附加回调函数。
2. 一个 Deferred 对象开始于挂起状态。任何使用 deferred.then(), deferred.always(), deferred.done(), 或者 deferred.fail() 添加到这个对象的回调函数都是排队等待执行的。调用 deferred.resolve() 或 eferred.resolveWith() 转换延迟到解决状态后立即执行设置的 doneCallbacks 。调用 deferred.reject() 或 deferred.rejectWith() 转换延迟到拒绝状态后立即执行设置的 failCallbacks 。一旦对象已经进入了解决或拒绝状态,它保持该状态。回调仍然可以添加到已解决或已拒绝的 Deferred 对象——它们会立即执行。
deferred.promise() 函数返回 Deferred(延迟)的 Promise 对象。
注意:1. 方法允许一个异步函数阻止那些干涉其内部请求的进度(progress)或状态(status)的其它代码。
2. 只包含 deferred 对象的一组方法,包括:done(),then(),fail(),isResolved(), isRejected(), always(), 这些方法只能观察一个 deferred 的状态,而无法更改 deferred 对象的内在状态。
3. deferred.promise()也可以接受一个 target 参数,此时传入的 target 将被赋予 Promise 的方法,并作为结果返回,而不是创建一个新对象。
$(function () { function asyncEvent(){ var dfd = new jQuery.Deferred(); // 在一个随机的时间间隔之后 Resolve (解决状态) setTimeout(function(){ dfd.resolve("欢呼"); }, Math.floor(400+Math.random()*2000)); // 在一个随机的时间间隔之后 reject (拒绝状态) setTimeout(function(){ dfd.reject("对不起"); }, Math.floor(400+Math.random()*2000)); // 每半秒显示一个"working..."消息 setTimeout(function working(){ if ( dfd.state() === "pending" ) { dfd.notify("working... "); setTimeout(working, 500); } }, 1); // 返回 Promise 对象,调用者不能改变延迟对象 return dfd.promise(); } // 为异步函数附加一个done, fail, 和 progress 处理程序 $.when( asyncEvent() ).then( function(status){ alert( status+', 事情进展顺利' ); }, function(status){ alert( status+', 这次你失败了' ); }, function(status){ $("body").append(status); } ); })
Deferred对象是jquery开发团队设计的,为了增强jquery的回调功能,在1.7版本中加入了jquery.
defer英语意思是延迟的意思.那么他是如何延迟的呢?
首先来理解一下基本知识,要不然后面没法说了.
$.Deferred([fun])
$.Deferred()没有参数时 返回Deferred对象;
有参数时,表示在这个参数上做延迟或异步操作,并且返回Deferred对象.
done(fn) 当延迟成功后调用该方法
fail(fn) 当延迟失败时调用失败
then(done,fail) 这个是done和fail的总写方式
always(fn) 不管延迟执行的成功还是失败,都执行的方法
上面的fn可能是一个function,也有可能是多个以逗号分隔的function函数
resolve和reject方法一旦执行,表示开始执行done,fail,then,always方法,
注意Deferred对象可以一次挂接多个done,fail方法,按照你分布的顺序依次执行.
resolve(value) 告诉对象执行done回调,value是参数
reject(value) 告诉对象执行fail回调,value是参数.
function pop(arg) { if(!arg){ console.error('pop title is requried'); } var conf={}, $box, $mask, $title, $content, $confirm, $cancel, dfd, confirmed, timer; if(typeof arg=='string') conf.title=arg; else { conf=$.extend(conf,arg); } dfd=$.Deferred(); $box=$('<div>' + '<div class="pop-title">'+conf.title+'</div>'+ '<div class="pop-content">' + '<div><button style="margin-right: 5px" class="primary confirm">确定</button>' + '<button class="cancel">取消</button></div>'+ '</div>'+ '</div>') .css({ color:'#444', width:240, height:'auto', padding:'15px 10px', background:'#fff', position:'fixed', 'border-radius':3, 'box-shadow':'0 1px 2px rgba(0,0,0,.5)' }) $title=$box.find('.pop-title').css({ padding:'5px 10px', 'font-weight':900, 'font-size':20, 'text-align':'center' }) $content=$box.find('.pop-content').css({ padding:'5px 10px', /*'font-weight':900,*/ 'text-align':'center' }) $confirm=$content.find('button.confirm'); $cancel=$content.find('button.cancel'); $mask=$('<div></div>') .css({ position:'fixed', background:'rgba(0,0,0,.5)', top:0, bottom:0, right:0, left:0 }); timer=setInterval(function () { if(confirmed!==undefined){ dfd.resolve(confirmed);//resolve(value),告诉对象执行done回调,value是参数(这里confirmed作为参数传给r) clearInterval(timer); dismiss_pop(); } },50); $confirm.on('click',function () { confirmed=true; /*console.log('confirmed',confirmed);*/ }); $cancel.on('click',on_cancel); $mask.on('click',on_cancel); function on_cancel() { confirmed=false; } function dismiss_pop() { $mask.remove(); $box.remove(); } function adjust_box_position() { var window_width=$window.width(), window_height=$window.height(), box_width=$box.width(), box_height=$box.height(), move_x, move_y; move_x=(window_width-box_width)/2; move_y=((window_height-box_height)/2)-20; $box.css({ left:move_x, top:move_y }) } $window.on('resize',function () { adjust_box_position(); }) $mask.appendTo($body); $box.appendTo($body); $window.resize(); return dfd.promise(); }
function listen_task_delete() { $delete_task_trigger.on('click',function () { var $this=$(this); /*找到删除按钮所在的元素*/ var $item=$this.parent().parent(); var index=$item.data('index');/*调用data-index属性*/ //如果用confirm会将后台全部暂停 var tmp = pop('确定删除?').then(function (r) { r?delete_task(index):null; }) }) }
接下来我们关注下,为什么要用deferred.promise() 函数返回 Deferred(延迟)的 Promise 对象。
首先看这样一段代码
var dfd=$.Deferred(); function delay(dfd){ var bool=true; setTimeout(function(){ alert("delay的setTimeout执行啦!"); if(bool){ dfd.resolve("done"); }else{ dfd.reject("fail"); } },2000); return dfd; } $.when(delay(dfd)) .done(function(value){ alert(value); }).fail(function(value){ alert(value); });
这个时候实现了延迟,回到功能.
上面说了 done只要一遇到resolve或fail遇到reject就会立即执行,
那么我们在底部添加一行代码:
var dfd=$.Deferred(); function delay(dfd){ var bool=true; setTimeout(function(){ console.log("delay的setTimeout执行啦!"); if(bool){ dfd.resolve("done"); }else{ dfd.reject("fail"); } },2000); return dfd; } $.when(delay(dfd)) .done(function(value){ console.log("done1"+value); }).fail(function(value){ console.log("fail1"+value); }); dfd.resolve("我是来捣乱的....");
很明显破坏了我们模拟延迟的目的.(我们的目的:将done里面的函数延迟)
将dfd从全局改成局部变量,这样别人就不能轻易的改变状态了.
function delay(){ var dfd=$.Deferred(); var bool=true; setTimeout(function(){ alert("delay的setTimeout执行啦!"); if(bool){ dfd.resolve("done"); }else{ dfd.reject("fail"); } },2000); return dfd; } $.when(delay()) .done(function(value){ alert("done1"+value); }).fail(function(value){ alert("fail1"+value); });
但是!!!done只要一遇到resolve或fail遇到reject就会立即执行
function delay(){ var dfd=$.Deferred(); var bool=true; setTimeout(function(){ console.log("delay的setTimeout执行啦!"); if(bool){ dfd.resolve("done"); }else{ dfd.reject("fail"); } },2000); return dfd; } var delay2=delay(); delay2.resolve(); $.when(delay()) .done(function(value){ console.log("done1"+value); }).fail(function(value){ console.log("fail1"+value); });
如果我们的Deferred可以这样任意的被篡改,那么我们的程序健壮何在,
有没有一个办法让外人无法访问到Deferred对象,但是又不影响我们的回调函数的调用.
答案是有:promise();
function delay(){ var dfd=$.Deferred(); var bool=true; setTimeout(function(){ console.log("delay的setTimeout执行啦!"); if(bool){ dfd.resolve("done"); }else{ dfd.reject("fail"); } },2000); return dfd.promise(); } //var delay2=delay(); //delay2.resolve(); $.when(delay()) .done(function(value){ console.log("done1"+value); }).fail(function(value){ console.log("fail1"+value); });
这个时候你在把注释放开,就会报错了.