在jQuery 1.5中使用deferred对象的代码(翻译)


Posted in Javascript onMarch 10, 2011

译者注:
1. Deferred是jQuery1.5新增的一个特性,很多人把它翻译成 “异步队列”,我觉得比较靠谱,毕竟和“延迟”没啥关系,不过这篇文章中我还采用deferred这个单词。

2. 这篇文章在jQuery1.5发布博客中提到,也是目前介绍deferred比较经典和深入的文章。鉴于目前中文资料比较少,特别翻译出来供大家学习参考。

3. 通篇采用意译的方式,如有不当还请大家提出。

jQuery1.5中新增的Deferreds对象,可以将任务完成的处理方式与任务本身解耦合。这在JavaScript社区没什么新意,因为Mochikit和Dojo两个JS框架已经实现了这个特性很长一段时间了。但是随着Julian Aubourg对jQuery1.5中AJAX模块的重写,deferreds理所当然成为了内部的实现逻辑。使用deferreds对象,多个回调函数可以被绑定在任务完成时执行,甚至可以在任务完成后绑定这些回调函数。这些任务可以是异步的,也可以是同步的。

更重要的是,deferreds已经作为$.ajax()的内部实现,所以你可以在调用AJAX时自动获取deferreds带来的遍历。比如我们可以这样绑定回调函数:

// $.get, 异步的AJAX请求 
var req = $.get('foo.htm').success(function (response) { 
// AJAX成功后的处理函数 
}).error(function () { 
// AJAX失败后处理函数 
}); 
// 这个函数有可能在AJAX结束前调用 
doSomethingAwesome(); 
// 添加另外一个AJAX回调函数,此时AJAX或许已经结束,或许还没有结束 
// 由于$.ajax内置了deferred的支持,所以我们可以这样写 
req.success(function (response) { 
// 这个函数会在AJAX结束后被调用,或者立即被调用如果AJAX已经结束 
});

我们不再被限制到只有一个成功,失败或者完成的回调函数了。相反这些随时被添加的回调函数被放置在一个先进先出的队列中。
从上面例子看出,回调函数可以被附加到AJAX请求中(任何可观察的任务observable task),甚至在AJAX请求已经结束。对于代码的组织是很好的,我们再也不用写很长的回调函数了。这就像$.queue()遇到了pub/sub(发布订阅机制,一般用在基于事件处理的模型中).
更深入一些,想象这样一个场景,在一些并发的AJAX请求全部结束之后执行一个回调函数。我可以方便的通过jQuery的函数$.when()来完成:
function doAjax() { 
return $.get('foo.htm'); 
} function doMoreAjax() { 
return $.get('bar.htm'); 
} 
$.when(doAjax(), doMoreAjax()).then(function () { 
console.log('I fire once BOTH ajax requests have completed!'); 
}).fail(function () { 
console.log('I fire if one or more requests failed.'); 
});

在jsFiddle中打开示例

上面的示例能够正常运行,这要归功于每个jQuery的AJAX方法返回值都包含一个promise函数,用来跟踪异步请求。Promise函数的返回值是deferred对象的一个只读视图。(The promise is a read-only view into the result of the task.Deferreds通过检测对象中是否存在promise()函数来判断当前对象是否可观察。$.when()会等待所有的AJAX请求结束,然后调用通过 .then(), .fail()注册的回调函数(具体调用哪些回调函数取决于任务的结束状态)。这些回调函数会按照他们的注册顺序执行。

更好的是,$.when()接受函数或者函数的数组为参数(译者注:这点不大对,$.when接受一个或多个deferred对象,或者原生的JS对象。注意不能以函数数组为参数),这样你就可以随意组合这些异步任务。

$.ajax()返回一个对象,这个对象关联一些deferred函数,比如promise(), then(), success(), error()。然而你不能操作原始的deferred对象,只有promise()函数(译者注:还记得刚才提到的promise是只读视图),以及可以检测deferred状态的isRejected() 以及isResolved()函数。

但是为什么不返回deferred对象呢?如果返回了完整的deferred对象,那么我们就拥有更多的控制,或许可以随意的触发(译者注:我把resolve翻译成触发,就是触发所有注册到deferred对象上的回调函数)deferred对象,从而导致所有回调函数在AJAX请求结束之前执行。因此,为了避免不期望的触发deferred的风险,我们应该只返回dfd.promise().(Therefore, to avoid potentially breaking the whole paradigm, only return the dfd.promise().)(译者注:如果你很迷惑上面几段话的确切意思,没关系,我随后会写一篇文章深层次分析其中原因
注册回调函数(Registering Callbacks)
上面的例子中,我们使用then(), success(), fail()方法来注册回调函数,其实还有更多的方法可以使用,特别在处理AJAX请求时。具体使用哪种方式取决于你对结果状态的关注。
所有deferred对象都有的函数 (AJAX, $.when 或者手工创建的deferred对象):

.then( doneCallbacks, failedCallbacks ) 
.done( doneCallbacks ) 
.fail( failCallbacks )

AJAX对象包含3个额外的方法,其中两个会映射到上面提到的方法。这些方法主要是为了兼容以前的代码:
// "success" 和 "error" 会分别映射到 "done" and "fail" 两个方法 
.success( doneCallbacks ) 
.error( failCallbacks )

你也可以注册一个complete的回调函数,它会在请求结束后调用,而不管这个请求是成功或者失败。不像success或者error函数,complete函数其实是一个单独的deferred对象的done函数别名。这个在$.ajax()内部创建的deferred对象,会在AJAX结束后触发回调函数(resolve)。
.complete( completeCallbacks )

因此,下面的3个例子是等价的(在AJAX的上下文中,success看起来比done函数会舒服点,对么?)(译者注:其实是因为我们熟悉以前的AJAX调用方式,先入为主罢了,或者叫思维定势):
$.get("/foo/").done( fn ); 
// 等价于: 
$.get("/foo/").success( fn ); 
// 等价于: 
$.get("/foo/", fn );

创建自己的deferred对象(Creating your own Deferred)
我们知道$.ajax和$.when在内部实现了deferred接口,不过我们也可以手工创建deferred对象:
function getData() { 
return $.get('/foo/'); 
} 
function showDiv() { 
var dfd = $.Deferred(); 
$('#foo').fadeIn(1000, dfd.resolve); 
return dfd.promise(); 
} 
$.when(getData(), showDiv()).then(function (ajaxResult) { 
console.log('The animation AND the AJAX request are both done!'); 
// 'ajaxResult'是服务器端返回(译者注:也就是getData中AJAX的结果) 
});

在jsFiddle中打开示例
在showDiv()中,我们创建了一个deferred对象,执行了一段动画,然后返回promise。这个deferred对象会在fadeIn()结束后被触发(resolved)。在这个promise返回和deferred对象(注意:这里的deferred指的是$.when创建的对象,而非showDiv()返回的对象)触发的中间,一个then()回调函数会被注册。这个回调函数会在两个异步的任务全部结束后执行。
getData()返回一个对象(译者注:其实是jQuery封装的XMLHttpRequest对象)拥有promise方法,这就允许$.when()监视本次AJAX请求的结束。The manually steps we took to return a promise in showDiv() is handled for us internally by $.ajax() and $.when().
1/15/2011: Julian在评论中指出,上面的语法可以被简化为$.Deferred(fn).promise()。因此下面的两端代码是等价的:
function showDiv() { 
var dfd = $.Deferred(); 
$('#foo').fadeIn(1000, dfd.resolve); 
return dfd.promise(); 
} 
// 等价于: 
function showDiv() { 
return $.Deferred(function (dfd) { 
$('#foo').fadeIn(1000, dfd.resolve); 
}).promise(); 
}

为自定义的deferred对象添加回调函数(Defer your Deferreds)
我们可以更进一步,为getData()和showDiv()分别注册回调函数,如同我们在$.then()中注册回调函数一样。(译者注:下面的段落内容重复,说的都是一个意思,就不翻译了,看代码吧)
function getData() { 
return $.get('/foo/').success(function () { 
console.log('Fires after the AJAX request succeeds'); 
}); 
} 
function showDiv() { 
return $.Deferred(function (dfd) { 
// 译者注:这段代码是原文没有的,但是在jsFiddle中出现。 
// 我觉得这是作者的原意,为自定义的deferred函数注册回调函数 
dfd.done(function () { 
console.log('Fires after the animation succeeds'); 
}); 
$('#foo').fadeIn(1000, dfd.resolve); 
}).promise(); 
} 
$.when(getData(), showDiv()).then(function (ajaxResult) { 
console.log('Fires after BOTH showDiv() AND the AJAX request succeed!'); 
// 'ajaxResult'是服务器返回结果 
});

在jsFiddle中打开示例
链式代码(Chaining Hotness)
Deferred的回调函数可以链式调用,只要函数返回的是deferred对象(译者注:dfd.promise()返回的是只读的deferred对象)。这是一个实际的代码 (via @ajpiano!)
function saveContact(row) { 
var form = $.tmpl(templates["contact-form"]), 
valid = true, 
messages = [], 
dfd = $.Deferred(); 
/* 
* 这里方式客户端验证代码 
*/ 
if (!valid) { 
dfd.resolve({ 
success: false, 
errors: messages 
}); 
} else { 
form.ajaxSubmit({ 
dataType: "json", 
success: dfd.resolve, 
error: dfd.reject 
}); 
} 
return dfd.promise(); 
}; 
saveContact(row).then(function (response) { 
if (response.success) { 
// 客户端验证通过,并且保存数据成功 
} else { 
// 客户端验证失败 
// 输出错误信息 
} 
}).fail(function (err) { 
// AJAX请求失败 
});

saveContact()函数首先验证表单数据的有效性,然后把有效性状态保存在变量valid中。如果验证失败,直接deferred会被触发(把一个包含success状态码和错误信息的JS对象作为参数传递给回调函数)。如果验证通过,则向服务器提交数据,在AJAX成功完成后触发deferred对象。fail()会处理404, 500等可以阻止AJAX请求成功完成的HTTP状态码。
不可观察的任务(Non-observable Tasks)
Deferreds对于解耦任务与任务处理函数时非常有用,而不管是异步任务或者同步任务。一个任务可能会返回promise,但也可以返回字符串,对象或者其他类型。
在这个例子中,当“Lanch Application”链接被首次点击时,一个AJAX请求会发送到服务器并返回当前时间戳。然后这个时间戳会被保存到这个链接的data缓存中。当这个链接再次被点击时,只是简单的从缓存中取出这个时间戳返回,而不会发出AJAX请求。
function startTask(element) { 
var timestamp = $.data(element, 'timestamp'); 
if (timestamp) { 
return timestamp; 
} else { 
return $.get('/start-task/').success(function (timestamp) { 
$.data(element, 'timestamp', timestamp); 
}); 
} 
} 
$('#launchApplication').bind('click', function (event) { 
event.preventDefault(); 
$.when(startTask(this)).done(function (timestamp) { 
$('#status').html('<p>You first started this task on: ' + timestamp + '</p>'); 
}); 
loadApplication(); 
});

当$.when()发现它的第一个参数没有promise函数(因此不可观察),它就会创建一个新的deferred对象,触发deferred对象,并返回promise只读对象。因此,任意不可观察的任务也能传递到$.when()中。
需要注意的一个问题是,如果一个对象自身拥有promise函数,则这个对象将不能作为deferred对象。jQuery判断一个对象是否deferred,是通过查看它是否有promise函数来决定的,但是jQuery并不会检查这个promise是否真的返回一个可用的对象。因此下面的代码将会出错:
var obj = { 
promise: function () { 
// do something 
} 
}; 
$.when(obj).then(fn);

结论(Conclusion)
Deferreds提出了一种新的健壮的方式来处理异步任务。和传统的将代码组织到一个回调函数中不同,新的deferred对象允许我们在任何时候(甚至在任务结束后)绑定多个回调函数,而这些回调函数会以先进先出的方式被调用。这篇文章中的信息可能比较难以消化,不过一旦你掌握了deferred对象的使用,你会发现组织异步执行的代码将会非常容易。
本文章由三生石上原创,博客园首发,转载请注明出处

Javascript 相关文章推荐
使用Mootools动态添加Css样式表代码,兼容各浏览器
Dec 12 Javascript
ExtJs纵坐标值重复问题的解决方法
Feb 27 Javascript
jQuery的animate函数学习记录
Aug 08 Javascript
Angularjs实现多个页面共享数据的方式
Mar 29 Javascript
原生js二级联动效果
Jun 20 Javascript
JavaScript之DOM插入更新删除_动力节点Java学院整理
Jul 03 Javascript
使用Vue的slot插槽分发父组件内容实现高度复用、更加灵活的组件(推荐)
May 01 Javascript
javascript异步编程的六种方式总结
May 17 Javascript
vue实现的上拉加载更多数据/分页功能示例
May 25 Javascript
vue实现滑动到底部加载更多效果
Oct 27 Javascript
解决vue-cli项目开发运行时内存暴涨卡死电脑问题
Oct 29 Javascript
JavaScript canvas实现雨滴特效
Jan 10 Javascript
基于Jquery的跨域传输数据(JSONP)
Mar 10 #Javascript
jQuery.autocomplete 支持中文输入(firefox)修正方法
Mar 10 #Javascript
Jquery中getJSON在asp.net中的使用说明
Mar 10 #Javascript
JQuery中的$.getJSON 使用说明
Mar 10 #Javascript
基于jquery的地址栏射击游戏代码
Mar 10 #Javascript
基于jquery的无缝循环新闻列表插件
Mar 07 #Javascript
JavaScript对象之间的转换 jQuery对象和原声DOM
Mar 07 #Javascript
You might like
PHP安装攻略:常见问题解答(一)
2006/10/09 PHP
PHP输出XML到页面的3种方法详解
2013/06/06 PHP
js限制checkbox勾选的个数以及php获取多个checkbbox的方法深入解析
2013/07/18 PHP
解决php写入数据库乱码的问题
2019/09/17 PHP
JS判断页面加载状态以及添加遮罩和缓冲动画的代码
2012/10/11 Javascript
javascript重写alert方法的实例代码
2013/03/29 Javascript
javascript每日必学之封装
2016/02/23 Javascript
javascript css红色经典选项卡效果实现代码
2016/05/17 Javascript
AngularJS中的缓存使用
2017/01/11 Javascript
一篇看懂vuejs的状态管理神器 vuex状态管理模式
2017/04/20 Javascript
vue双花括号的使用方法 附练习题
2017/11/07 Javascript
基于js文件加载优化(详解)
2018/01/03 Javascript
Angular Material Icon使用详解
2018/11/07 Javascript
JavaScript中BOM对象原理与用法分析
2019/07/09 Javascript
Vue路由管理器Vue-router的使用方法详解
2020/02/05 Javascript
windows下create-react-app 升级至3.3.1版本踩坑记
2020/02/17 Javascript
在漏洞利用Python代码真的很爽
2007/08/26 Python
详解Python中的各种函数的使用
2015/05/24 Python
python 用lambda函数替换for循环的方法
2018/06/09 Python
python 按不同维度求和,最值,均值的实例
2018/06/28 Python
Python面向对象之继承和组合用法实例分析
2018/08/27 Python
Python3.5面向对象程序设计之类的继承和多态详解
2019/04/24 Python
django+echart数据动态显示的例子
2019/08/12 Python
python绘制无向图度分布曲线示例
2019/11/22 Python
tensorflow求导和梯度计算实例
2020/01/23 Python
python+selenium+Chrome options参数的使用
2020/03/18 Python
HTML5 在canvas中绘制文本附效果图
2014/06/23 HTML / CSS
英国高级健康和美容产品零售商:Life and Looks
2019/08/01 全球购物
英国比较机场停车场网站:Airport Parking Essentials
2019/12/01 全球购物
C# .NET面试题
2015/11/28 面试题
军训自我鉴定200字
2014/02/13 职场文书
校长一岗双责责任书
2015/05/09 职场文书
2015最新婚礼司仪主持词
2015/06/30 职场文书
2015年第31个教师节致辞
2015/07/31 职场文书
六年级作文之关于梦
2019/10/22 职场文书
nginx简单配置多个server的方法
2021/03/31 Servers