异步机制
Javascript编程往往会伴随着异步编程,比如远程获取数据,大量的异步编程会带来许多的回调函数,js是单线程的,所以完成异步往往需要借助浏览器事件驱动,这样会让我们的代码和算法变得支离破碎。jQuery就提供了一个抽象的 非阻塞
解决方案: Deferred
。
认识异步
alert(1)
setTimeout(function(){alert(2)
},0)
alert(3)
//alert(1)
//alert(3)
//alert(2)
一、认识 deferred
对象
开发网站的过程中,我们经常遇到某些耗时很长的Javascript操作。其中,既有异步的操作(比如ajax读取服务器数据)
,也有同步的操作(比如遍历一个大型数组)
,它们都不是立即能得到结果的。
通常的做法是,为它们指定回调函数(callback)
。即事先规定,一旦它们运行结束,应该调用哪些函数。
deferred
对象是在jquery 1.5版本被引进来的,jquery在回调方面的出处理不是很好,为此就诞生了deferred对象了。deferred
对象就是jQuery
的回调函数解决方案。在英语中,defer
的意思是"延迟",所以deferred对象的含义就是"延迟"到未来某个点再执行。
二、ajax的链式操作
最开始写法是:
// <1.5
$.ajax({url: "test.html",success: function(){alert("哈哈&#xff0c;成功了&#xff01;");},error:function(){alert("出错啦&#xff01;");}});
如果jquery
的版本是1.5
以下的&#xff0c;那么返回的是XHR对象
&#xff0c;所以没法链式操作&#xff0c;但是高于1.5的版本的话&#xff0c;返回的是Deferred
对象,可以进行链式操作。
// >&#61;1.5
$.ajax("test.html").done(function(){}).fail(function(){})
done()相当于success方法&#xff0c;fail()相当于error方法。采用链式写法以后&#xff0c;代码的可读性大大提高。
三、指定同一操作的多个回调函数
deferred对象的一大好处&#xff0c;就是它允许你自由添加多个回调函数。
还是以上面的代码为例&#xff0c;如果ajax操作成功后&#xff0c;除了原来的回调函数&#xff0c;我还想再运行一个回调函数&#xff0c;怎么办&#xff1f;
$.ajax("test.html").done(function(){ alert("哈哈&#xff0c;成功了&#xff01;");} ).fail(function(){ alert("出错啦&#xff01;"); } ).done(function(){ alert("第二个回调函数&#xff01;");} );
四、为多个操作指定回调函数
deferred对象的另一大好处&#xff0c;就是它允许你为多个事件指定一个回调函数&#xff0c;这是传统写法做不到的。那就是$.when()
$.when($.ajax("test1.html"), $.ajax("test2.html")).done(function(){ alert("哈哈&#xff0c;成功了&#xff01;"); }).fail(function(){ alert("出错啦&#xff01;"); });
//$.ajax("test1.html")和$.ajax("test2.html")&#xff0c;如果都成功了&#xff0c;就运行done()指定的回调函数&#xff1b;如果有一个失败或都失败了&#xff0c;就执行fail()指定的回调函数。
五、普通操作的回调函数接口扩展
它把这一套回调函数接口&#xff0c;从ajax操作扩展到了所有操作。也就是说&#xff0c;任何一个操作----不管是ajax操作还是本地操作&#xff0c;也不管是异步操作还是同步操作----都可以使用deferred对象的各种方法&#xff0c;指定回调函数。
// 一个耗时操作
var wait &#61; function(){var task &#61; function(){alert(&#39;执行了&#39;);};setTimeout(5000);
}
我们为其添加回调,可能会想到$.when();
$.when(wait()).done(function(){ alert("哈哈&#xff0c;成功了&#xff01;"); }).fail(function(){ alert("出错啦&#xff01;"); });
但是没起到效果&#xff0c;done立即执行&#xff0c;原因是$.when()接受的是一个deferred对象。所以我们进一步改进吧
var dtd &#61; $.Deferred();
var wait &#61; function(){var task &#61; function(){alert(&#39;执行了&#39;);dtd.resolve(); // 改变deferred对象的执行状态};setTimeout(task, 5000);return dd;
}
这样wait()返回的是一个defferred对象了&#xff0c;这就可以上链式操作了。
$.when(wait()).done(function(){ alert("哈哈&#xff0c;成功了&#xff01;"); }).fail(function(){ alert("出错啦&#xff01;"); });
六、defferred.resolve() 和 defferred.reject()
deferred采用的是Promise的原理。这里我们要清楚一个执行状态的概念。在defferred对象中&#xff0c;有着三种状态&#xff0c;未完成
已完成
已失败
如果执行状态是"已完成"
&#xff08;resolved&#xff09;,deferred对象立刻调用done()方法
指定的回调函数&#xff1b;
如果执行状态是"已失败"
&#xff0c;调用fail()
方法指定的回调函数&#xff1b;
如果执行状态是"未完成"
&#xff0c;则继续等待&#xff0c;或者调用progress()
方法指定的回调函数&#xff08;jQuery1.7版本添加&#xff09;。
defferred.resolve()
是将状态从未完成
变为已完成
。 &#61;&#61;> done()defferred.reject()
是将状态从未完成
变为了已失败
。 &#61;&#61;> fail()
var dtd &#61; $.Deferred(); // 新建一个Deferred对象var wait &#61; function(){var tasks &#61; function(){alert("执行完毕&#xff01;");dtd.resolve(); // 改变Deferred对象的执行状态};setTimeout(tasks,5000);return dtd; // 返回promise对象};$.when(wait()).done(function(){ alert("哈哈&#xff0c;成功了&#xff01;"); }).fail(function(){ alert("出错啦&#xff01;"); });dtd.resolve();// 这里会立即先执行一次
七、defferred.promise();
上面的方法是存在一些问题的&#xff0c;比如dtd是一个全局变量&#xff0c;他可以在外部被随时该边状态&#xff0c;这样就会使得done()或者fail()执行不只有一次。
这显然不是我们想要的&#xff0c;所以我们就需要defferred.promise的帮忙了。
它的作用是&#xff0c;在原来的deferred对象上返回另一个deferred对象&#xff0c;后者只开放与改变执行状态无关的方法&#xff08;比如done()方法和fail()方法&#xff09;&#xff0c;屏蔽与改变执行状态有关的方法&#xff08;比如resolve()方法和reject()方法&#xff09;&#xff0c;从而使得执行状态不能被改变。
var wait &#61; function(dtd){var dtd &#61; $.Deferred(); //在函数内部&#xff0c;新建一个Deferred对象var tasks &#61; function(){alert("执行完毕&#xff01;");dtd.resolve(); // 改变Deferred对象的执行状态};setTimeout(tasks,5000);return dtd.promise(); // 返回promise对象};var dd &#61; wait();// dd.resolve(); // 这里如果使用了的话&#xff0c;就会报错的哦&#xff0c;因为返回deferred.promise&#xff08;&#xff09;是无法对状态修改的哦$.when(dd).done(function(){ alert("哈哈&#xff0c;成功了&#xff01;"); }).fail(function(){ alert("出错啦&#xff01;"); });
八、$.Defferred(wait)
另一种防止执行状态改变的方法是使用$.Defferred()
var dtd &#61; $.Deferred(); // 新建一个Deferred对象var wait &#61; function(dtd){var tasks &#61; function(){alert("执行完毕&#xff01;");dtd.resolve(); // 改变Deferred对象的执行状态};setTimeout(tasks,5000);return dtd;};
var ddd &#61; wait();
// dd.resolve(); //这里报错了哦
$.Deferred(dd).done(function(){ alert("哈哈&#xff0c;成功了&#xff01;"); }).fail(function(){ alert("出错啦&#xff01;"); });
九、defferred对象小结
- $.Deferred() 生成一个deferred对象。
- deferred.done() 指定操作成功时的回调函数
- deferred.fail() 指定操作失败时的回调函数
- deferred.promise() 没有参数时&#xff0c;返回一个新的deferred对象&#xff0c;该对象的运行状态无法被改变&#xff1b;接受参数时&#xff0c;作用为在参数对象上部署deferred接口。
- deferred.resolve(args) 手动改变deferred对象的运行状态为"已完成"&#xff0c;从而立即触发done()方法。解决Deferred&#xff08;延迟&#xff09;对象&#xff0c;并根据给定的args参数调用任何完成回调函数&#xff08;doneCallbacks&#xff09;。
- deferred.resolveWith() 解决Deferred&#xff08;延迟&#xff09;对象&#xff0c;并根据给定的 context和args参数调用任何完成回调函数&#xff08;doneCallbacks&#xff09;。
- deferred.reject(args) 这个方法与deferred.resolve()正好相反&#xff0c;调用后将deferred对象的运行状态变为"已失败"&#xff0c;从而立即触发fail()方法。拒绝Deferred&#xff08;延迟&#xff09;对象&#xff0c;并根据给定的args参数调用任何失败回调函数&#xff08;failCallbacks&#xff09;。这里的args是一个对象。
- deferred.rejectWith(context, args) 拒绝Deferred&#xff08;延迟&#xff09;对象&#xff0c;并根据给定的 context和args参数调用任何失败回调函数&#xff08;failCallbacks&#xff09;。这里的args是一个数组类型。
- $.when() 为多个操作指定回调函数。除了这些方法以外&#xff0c;deferred对象还有二个重要方法&#xff0c;上面的教程中没有涉及到。Context&#xff08;上下文&#xff09; 作为 this对象传递给失败回调函数&#xff08;failCallbacks &#xff09;
- deferred.then()
有时为了省事&#xff0c;可以把done()和fail()合在一起写&#xff0c;这就是then()方法。
$.when($.ajax( "/main.php" )).then(successFunc, failureFunc );
如果then()有两个参数&#xff0c;那么第一个参数是done()方法的回调函数&#xff0c;第二个参数是fail()方法的回调方法。如果then()只有一个参数&#xff0c;那么等同于done()。
- deferred.always() 这个方法也是用来指定回调函数的&#xff0c;它的作用是&#xff0c;不管调用的
deferred.resolve()
还&#96;是deferred.reject()&#xff0c;最后总是执行。
$.ajax( "test.html" ).always( function() { alert("已执行&#xff01;");} );
- deferred.state() 确定一个Deferred&#xff08;延迟&#xff09;对象的当前状态。
- "pending": Deferred对象是尚未完成状态 (不是 "rejected" 或 "resolved").
- "resolved": Deferred对象是在解决状态&#xff0c;这意味着&#xff0c;deferred.resolve() 或者 deferred.resolveWith()被对象访问和doneCallbacks被访问&#xff08;或在被调用的过程中&#xff09;
- "rejected": Deferred对象是在被拒绝的状态&#xff0c;这意味着&#xff0c;deferred.reject() 或者 deferred.rejectWith() 被对象访问和failCallbacks被访问&#xff08;或在被调用的过程中&#xff09; 。
这种方法主要是用在调试&#xff0c;例如&#xff0c;在准备拒绝&#xff08;reject&#xff09;一个延迟对象前&#xff0c;判断它是否已经处于 resolved 状态。
十、使用实例
情景1&#xff1a;当用户按下删除弹窗的确定或取消后&#xff0c;把弹窗隐藏&#xff0c;并执行对应的操作&#xff08;删除或不执行&#xff09;&#xff0c;因为我们不知道用户什么时候会点击按钮&#xff0c;所以不能让弹窗阻塞其他任务的执行。
function pop(arg) {if (!arg) {console.error(&#39;pop title is empty&#39;);}var dfd &#61; $.Deferred() //实例化一个延迟对象 ,confirmed //记录按下确定或取消 , $confirm &#61; $content.find(&#39;button.confirm&#39;) //确认按钮 ,$cancel &#61; $content.find(&#39;button.cancel&#39;); //取消按钮 //定时器轮询&#xff0c;当按下确定或取消时触发删除或取消操作 timer &#61; setInterval(function() {if (confirmed !&#61;&#61; undifined) {dfd.resolve(confirmed);clearInterval(timer);dismiss_pop();}}, 50);//点击确定时更改confirmed状态 $confirm.on(&#39;click&#39;, function() {confirmed &#61; true;});//点击取消时更改confirmed状态 $cancel.on(&#39;click&#39;, function() {confirmed &#61; false;}); //返回dfd对象return dfd.promise();}$(&#39;.delete&#39;).click(function() {var $this &#61; $(this);var index &#61; $this.data(&#39;index&#39;);//当前的id //确定删除pop(&#39;确定删除&#xff1f;&#39;).then(function(res) {res ? delete_task(index) : null;})})
参考文献
阮一峰的defferred理解
allenm