原文:http://www.erichynds.com/jquery/using-deferreds-in-jquery/

翻译:三生石上(http://cnblogs.com/sanshi/)

译者注:

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().)(译者注:如果你很迷惑上面几段话的确切意思,没关系,我随后会写一篇文章深层次分析其中原因:http:/cnblogs.com/sanshi/)

注册回调函数(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对象的使用,你会发现组织异步执行的代码将会非常容易。

本文章由三生石上原创,博客园首发,转载请注明出处。

[翻译]在jQuery 1.5中使用deferred对象相关推荐

  1. 读jQuery之二十(Deferred对象)

    Deferred对象是由 jQuery.Deferred 构造的,jQuery.Deferred 被实现为简单工厂模式. 它用来解决JS中的异步编程,它遵循 Common Promise/A 规范.实 ...

  2. [轉]在jQuery1.5中使用deferred对象 - 拿着放大镜看Promise

    http://www.cnblogs.com/sanshi/archive/2011/03/11/1981789.html 不錯的JS方面的文章 三生石上

  3. ajax请求中带判断语句例子,jQuery中借助deferred来请求及判断AJAX加载的实例讲解...

    ajax请求异步队列加载我们在开发程序的时候通常会碰到使用ajax加载数据显示到列表的情况.ajax默认使用异步加载(async:true).为什么不使用同步呢,因为ajax同步加载会UI渲染线程阻塞 ...

  4. 谈谈JQuery的Deferred对象

    最近一个变态的项目,一个页面只含编辑器且有下载功能,大概含20个左右接口,要求数据完整显示(很多echarts图),弄个等待圈圈等它loading,启用jQuery的Deferred延迟对象,再多的接 ...

  5. 解析Jquery取得iframe中元素的几种方法

    iframe在复合文档中经常用到,利用jquery操作iframe可以大幅提高效率,这里收集一些基本操作,需要的朋友可以参考下 DOM方法:父窗口操作IFRAME:window.frames[&quo ...

  6. jQuery中deferred对象的使用(二)

    接上一回的内容,漏了一个always()方法,参数也是回调函数,与done和fail不同的是,无论任何情况都执行always方法中的回调. deferred对象的使用(二) deferred对象不光可 ...

  7. 深入理解jQuery中的Deferred

    深入理解jQuery中的Deferred 引入 1  在开发的过程中,我们经常遇到某些耗时很长的javascript操作,并且伴随着大量的异步. 2  比如我们有一个ajax的操作,这个ajax从发出 ...

  8. jQuery 中的 Deferred 和 Promises

    看前首先了解:Promises/A规范,具体可以看这里,http://bijian1013.iteye.com/blog/2392429 由于jQuery版本问题对Deferred对象的实现有所不同, ...

  9. jQuery中的Deferred详解和使用

    首先,为什么要使用Deferred? 先来看一段AJAX的代码: var data;$.get('api/data', function(resp) {data = resp.data;});doSo ...

最新文章

  1. Confluence 6 SQL Server 测试你的数据库连接
  2. python3 基本数据类型
  3. SQL Server-外部联接基础回顾(十三)
  4. 关于halcon多区域挑选有关算法的自我理解(tuple_sort_index)
  5. 2017 年编程语言排行榜:Python 排第一
  6. Nginx-1.18.0主函数main思维导图(第一版)
  7. [导入]基于Spring+zk的WebDisk系统研究.pdf(462.84 KB)
  8. 将byte[]转为各种进制的字符串
  9. 如何用 JavaScript 控制 Arduino?
  10. 天线远场定义_天线近场与远场的划分(转)
  11. 前端文件下载兼容方案(兼容主流浏览器,包括IE与Safari)
  12. 【Leetcode刷题Python】714. 买卖股票的最佳时机含手续费
  13. Testin云测平台
  14. Linux/Windows快速镜像安装包下载
  15. 机房动环监控系统应用意义
  16. Unix时间戳1970Java与Unix时间戳互转
  17. 《软件工程》第4章需求工程
  18. [RK3288][Android6.0] 调试笔记 --- user版本固件升级无法开机问题
  19. c语言中按照“|”字符串截取,shell中取字符串子串的几种方式 截取substr
  20. java基于springboot+vue的社区居民健康管理系统 nodejs 前后端分离

热门文章

  1. 夺命雷公狗---PDO NO:3与连接有关的选项
  2. 利用strut2标签自动生成form前端验证代码
  3. git 拉取代码失败
  4. SpringBoot使用RequestBodyAdvice进行统一参数处理
  5. Rainmeter后续——WIN10任务栏透明以及设置开机启动应用
  6. Java命名规范(建议收藏)
  7. PHP 递增/递减运算符
  8. 《微信小程序开发入门精要》——第2章,第2.8节带边距的水平等间隔排列
  9. Java编程的逻辑 (43) - 剖析TreeMap
  10. Codeforces Round #281 (Div. 2) A. Vasya and Football 模拟