在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 相关文章推荐
jquery 获取表单元素里面的值示例代码
Jul 28 Javascript
jQuery中odd选择器的定义和用法
Dec 23 Javascript
javascript中typeof操作符和constucor属性检测
Feb 26 Javascript
javascript最基本的函数汇总
Jun 25 Javascript
跟我学习javascript的函数和函数表达式
Nov 16 Javascript
Bootstrap table分页问题汇总
May 30 Javascript
bootstrap按钮插件(Button)使用方法解析
Jan 13 Javascript
微信小程序 跳转传递数据的实例
Jul 06 Javascript
jQuery使用bind函数实现绑定多个事件的方法
Oct 11 jQuery
js canvas实现写字动画效果
Nov 30 Javascript
javascript设计模式 ? 原型模式原理与应用实例分析
Apr 10 Javascript
vue项目页面嵌入代码块vue-prism-editor的实现
Oct 30 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代替JS玩转DOM的思路及示例代码
2014/06/15 PHP
基于递归实现的php树形菜单代码
2014/11/19 PHP
PHP树-不需要递归的实现方法
2016/06/21 PHP
解读IE和firefox下JScript和HREF的执行顺序
2008/01/12 Javascript
javascript网页关闭时提醒效果脚本
2008/10/22 Javascript
ImageZoom 图片放大镜效果(多功能扩展篇)
2010/04/14 Javascript
js ondocumentready onmouseover onclick onmouseout 样式
2010/07/22 Javascript
页面加载完毕后滚动条自动滚动一定位置
2014/02/20 Javascript
javascript 操作符(~、&amp;、|、^、)使用案例
2014/12/31 Javascript
Javascript实现飞动广告效果的方法
2015/05/25 Javascript
jquery插件NProgress.js制作网页加载进度条
2015/06/05 Javascript
jQuery Timelinr实现垂直水平时间轴插件(附源码下载)
2016/02/16 Javascript
javascirpt实现2个iframe之间传值的方法
2016/06/30 Javascript
jquery实现百叶窗效果
2017/01/12 Javascript
React-Native做一个文本输入框组件的实现代码
2017/08/10 Javascript
Express下采用bcryptjs进行密码加密的方法
2018/02/07 Javascript
原生js实现拖拽功能基本思路详解
2018/04/18 Javascript
浅谈微信JS-SDK 微信分享接口开发(介绍版)
2018/08/15 Javascript
vue动态改变背景图片demo分享
2018/09/13 Javascript
详解vue几种主动刷新的方法总结
2019/02/19 Javascript
Vue数字输入框组件使用方法详解
2020/02/10 Javascript
[02:51]DOTA2英雄基础教程 艾欧
2014/01/13 DOTA
[01:29:46]DOTA2上海特级锦标赛C组资格赛#1 OG VS LGD第二局
2016/02/27 DOTA
python生成随机验证码(中文验证码)示例
2014/04/03 Python
Django框架实现的简单分页功能示例
2018/12/04 Python
python3获取文件中url内容并下载代码实例
2019/12/27 Python
pytorch VGG11识别cifar10数据集(训练+预测单张输入图片操作)
2020/06/24 Python
windows10在visual studio2019下配置使用openCV4.3.0
2020/07/14 Python
白俄罗斯大卖场:21vek.by
2019/07/25 全球购物
shell程序如何生命变量?shell变量是弱变量吗?
2014/11/10 面试题
办公室主任职责范本
2014/03/07 职场文书
出国英文推荐信
2014/05/10 职场文书
个人授权委托书格式
2014/08/30 职场文书
2015年化妆品销售工作总结
2015/05/11 职场文书
AJAX学习笔记
2021/05/18 Javascript
java开发双人五子棋游戏
2022/05/06 Java/Android