在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 相关文章推荐
JavaScript 脚本将当地时间转换成其它时区
Mar 19 Javascript
JavaScript中yield实用简洁实现方式
Jun 12 Javascript
Jquery之Ajax运用 学习运用篇
Sep 26 Javascript
js模态对话框使用方法详解
Feb 16 Javascript
vue对storejs获取的数据进行处理时遇到的几种问题小结
Mar 20 Javascript
vue 组件使用中的一些细节点
Apr 25 Javascript
JS实现点击生成UUID的方法完整实例【基于jQuery】
Jun 12 jQuery
解决layui checkbox 提交多个值的问题
Sep 02 Javascript
layui 对弹窗 form表单赋值的实现方法
Sep 04 Javascript
Vue解析带html标签的字符串为dom的实例
Nov 13 Javascript
详细分析React 表单与事件
Jul 08 Javascript
jQuery实现放大镜案例
Oct 19 jQuery
基于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
一个ORACLE分页程序,挺实用的.
2006/10/09 PHP
php网站地图生成类示例
2014/01/13 PHP
php实现按文件名搜索文件的远程文件查找器
2014/05/10 PHP
Laravel5.1数据库连接、创建数据库、创建model及创建控制器的方法
2016/03/29 PHP
php基于jquery的ajax技术传递json数据简单实例
2016/04/15 PHP
php获取访问者浏览页面的浏览器类型
2017/01/23 PHP
老生常谈php 正则中的i,m,s,x,e分别表示什么
2017/03/02 PHP
ThinkPHP实现转换数据库查询结果数据到对应类型的方法
2017/11/16 PHP
JavaScript可否多线程? 深入理解JavaScript定时机制
2012/05/23 Javascript
JQuery插件开发示例代码
2013/11/06 Javascript
javascript实现鼠标移到Image上方时显示文字效果的方法
2015/08/07 Javascript
JavaScript禁止复制与粘贴的实现代码
2016/05/16 Javascript
JS实现iframe自适应高度的方法示例
2017/01/07 Javascript
JavaScript实现水平进度条拖拽效果
2017/01/18 Javascript
利用node.js搭建简单web服务器的方法教程
2017/02/20 Javascript
js实现图片懒加载效果
2017/07/17 Javascript
JavaScript Drum Kit 指南(纯 JS 模拟敲鼓效果)
2017/07/23 Javascript
Vue不能检测到Object/Array更新的情况的解决
2018/06/26 Javascript
微信小程序scroll-view实现滚动穿透和阻止滚动的方法
2018/08/20 Javascript
vue-router源码之history类的浅析
2019/05/21 Javascript
Vue中的this.$options.data()和this.$data用法说明
2020/07/26 Javascript
Vue+Openlayers自定义轨迹动画
2020/09/24 Javascript
解决vuex改变了state的值,但是页面没有更新的问题
2020/11/12 Javascript
javascript实现前端分页功能
2020/11/26 Javascript
Python多线程编程(二):启动线程的两种方法
2015/04/05 Python
Python 实现OpenCV格式和PIL.Image格式互转
2020/01/09 Python
家乐福巴西网上超市:Carrefour巴西
2016/10/31 全球购物
华美博弈C/VC工程师笔试试题
2012/07/16 面试题
医药专业应届毕业生求职信范文
2014/01/01 职场文书
揠苗助长教学反思
2014/02/04 职场文书
《小壁虎借尾巴》教学反思
2014/02/16 职场文书
爱岗敬业事迹材料
2014/12/24 职场文书
体育部部长竞选稿
2015/11/21 职场文书
给原生html中添加水印遮罩层的实现示例
2021/04/02 Javascript
python实现的web监控系统
2021/04/27 Python
anaconda python3.8安装后降级
2021/06/11 Python