在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 相关文章推荐
xml文档转换工具,附图表例子(hta)
Nov 17 Javascript
JavaScript去掉数组中的重复元素
Jan 13 Javascript
javaScript实现浮点数转十六进制字符
Oct 29 Javascript
JS取得绝对路径的实现代码
Jan 16 Javascript
微信小程序 监听手势滑动切换页面实例详解
Jun 15 Javascript
Vue 滚动行为的具体使用方法
Sep 13 Javascript
JavaScript实现获取select下拉框中第一个值的方法
Feb 06 Javascript
使用Angular CLI生成 Angular 5项目教程详解
Mar 18 Javascript
详解bootstrap-fileinput文件上传控件的亲身实践
Mar 21 Javascript
JS实现拼图游戏
Jan 29 Javascript
小程序跳转H5页面的方法步骤
Mar 06 Javascript
微信小程序中使用vant框架的具体步骤
Feb 18 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几种字符串连接的效率比较(详解)
2017/02/22 PHP
PHP进阶学习之反射基本概念与用法分析
2019/06/18 PHP
php探针使用原理和技巧讲解
2019/09/17 PHP
在页面上用action传递参数到后台出现乱码的解决方法
2013/12/31 Javascript
js中的onchange和onpropertychange (onchange无效的解决方法)
2014/03/08 Javascript
js用Date对象的setDate()函数对日期进行加减操作
2014/09/18 Javascript
jQuery结合ajax实现动态加载文本内容
2015/05/19 Javascript
jQuery中$.extend()用法实例
2015/06/24 Javascript
jQuery实现限制文本框的输入长度
2017/01/11 Javascript
浅谈regExp的test方法取得的值变化的原因及处理方法
2017/03/01 Javascript
Node.js发送HTTP客户端请求并显示响应结果的方法示例
2017/04/12 Javascript
Ionic2调用本地SQlite实例
2017/04/22 Javascript
Angularjs 事件指令详细整理
2017/07/27 Javascript
js实现音乐播放控制条
2017/09/09 Javascript
微信小程序 循环及嵌套循环的使用总结
2017/09/26 Javascript
使用live-server快速搭建本地服务器+自动刷新的方法
2018/03/09 Javascript
详解Vue结合后台的列表增删改案例
2018/08/21 Javascript
webpack4 CSS Tree Shaking的使用
2018/09/03 Javascript
three.js实现圆柱体
2018/12/30 Javascript
layui 选择列表,打勾,点击确定返回数据的例子
2019/09/02 Javascript
[32:47]完美世界DOTA2联赛 GXR vs IO 第二场 11.07
2020/11/09 DOTA
[48:31]DOTA2-DPC中国联赛 正赛 Dynasty vs XG BO3 第一场 2月2日
2021/03/11 DOTA
Python中用sleep()方法操作时间的教程
2015/05/22 Python
Python算法应用实战之队列详解
2017/02/04 Python
Python 快速实现CLI 应用程序的脚手架
2017/12/05 Python
Django中提供的6种缓存方式详解
2019/08/05 Python
解决TensorFlow模型恢复报错的问题
2020/02/06 Python
如何使用python写截屏小工具
2020/09/29 Python
CSS3结构性伪类选择器九种写法
2012/04/18 HTML / CSS
C#软件工程师英语面试题
2015/06/07 面试题
工业学校毕业生自荐书
2014/01/03 职场文书
幼儿教师自我剖析材料
2014/09/29 职场文书
导游词400字
2015/02/13 职场文书
2015年档案管理员工作总结
2015/05/13 职场文书
催款函怎么写
2015/06/24 职场文书
辞职申请书范本
2019/05/20 职场文书