在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 相关文章推荐
HTML TO JavaScript 转换
Jun 26 Javascript
jQuery 名称冲突的解决方法
Apr 08 Javascript
获取div编辑框,textarea,input text的光标位置 兼容IE,FF和Chrome的方法介绍
Nov 08 Javascript
JS操作JSON要领详细总结
Aug 25 Javascript
jquery的ajax异步请求接收返回json数据实例
Jun 16 Javascript
Javascript实现计算个人所得税
May 10 Javascript
javascript将中国数字格式转换成欧式数字格式的简单实例
Aug 02 Javascript
Angularjs实现mvvm式的选项卡示例代码
Sep 08 Javascript
详解如何较好的使用js
Dec 16 Javascript
微信小程序中实现一对多发消息详解及实例代码
Feb 14 Javascript
ant-design-vue 快速避坑指南(推荐)
Jan 21 Javascript
Vue 的 v-model用法实例
Nov 23 Vue.js
基于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判断字符以及字符串的包含方法属性
2008/08/30 PHP
php 短链接算法收集与分析
2011/12/30 PHP
php获取apk包信息的方法
2014/08/15 PHP
PHP封装的简单连接MongoDB类示例
2019/02/13 PHP
AeroWindow 基于JQuery的弹出窗口插件
2011/06/27 Javascript
基于jquery的滚动鼠标放大缩小图片效果
2011/10/27 Javascript
教你如何自定义百度分享插件以及bshare分享插件的分享按钮
2014/06/20 Javascript
Node.js安装教程和NPM包管理器使用详解
2014/08/16 Javascript
js读取json的两种常用方法示例介绍
2014/10/19 Javascript
JavaScript获得url所有参数键值表的方法
2015/03/21 Javascript
JS显示下拉列表框内全部元素的方法
2015/03/31 Javascript
jQuery操作属性和样式详解
2016/04/13 Javascript
JavaScript对象创建模式实例汇总
2016/10/03 Javascript
node作为中间服务层如何发送请求(发送请求的实现方法详解)
2018/01/02 Javascript
vue2.0之多页面的开发的示例
2018/01/30 Javascript
详解Immutable及 React 中实践
2018/03/01 Javascript
jQuery实现条件搜索查询、实时取值及升降序排序的方法分析
2019/05/04 jQuery
JS函数基本定义与用法示例
2020/01/15 Javascript
微信小程序清空输入框信息与实现屏幕往上滚动的示例代码
2020/06/23 Javascript
Python实现字典的遍历与排序功能示例
2017/12/23 Python
基于python内置函数与匿名函数详解
2018/01/09 Python
python如何读写csv数据
2018/03/21 Python
Puppeteer使用示例详解
2019/06/20 Python
kafka监控获取指定topic的消息总量示例
2019/12/23 Python
PyCharm第一次安装及使用教程
2020/01/08 Python
python GUI库图形界面开发之PyQt5控件数据拖曳Drag与Drop详细使用方法与实例
2020/02/27 Python
CSS3圆角边框和边界图片效果实例
2016/07/01 HTML / CSS
用HTML5.0制作网页的教程
2010/05/30 HTML / CSS
描述Cookie和Session的作用,区别和各自的应用范围,Session工作原理
2015/03/25 面试题
八皇后问题,输出了所有情况,不过有些结果只是旋转了90度
2016/08/15 面试题
物流仓储实习自我鉴定
2013/09/25 职场文书
优秀工会工作者事迹材料
2014/06/02 职场文书
居委会工作总结2015
2015/05/18 职场文书
教务处干事工作总结
2015/08/14 职场文书
2016年度基层党建工作公开承诺书
2016/03/25 职场文书
阿里云日志过滤器配置日志服务
2022/04/09 Servers