澳门新萄京官方网站-www.8455.com-澳门新萄京赌场网址

澳门新萄京官方网站:jQuery的Deferred对象概述,

2019-10-01 作者:澳门新萄京赌场网址   |   浏览(106)

jQuery的Deferred对象概述

2017/03/02 · CSS

本文由 伯乐在线 - HansDo 翻译,叙帝利 校稿。未经许可,禁止转载!
英文出处:Aurelio De Rosa。欢迎加入翻译组。

很久以来,JavaScript 开发者们习惯用回调函数的方式来执行一些任务。最常见的例子就是利用 addEventListener()函数来添加一个回调函数, 用来在指定的事件(如 clickkeypress)被触发时,执行一系列的操作。回调函数简单有效——在逻辑并不复杂的时候。遗憾的是,一旦页面的复杂度增加,而你因此需要执行很多并行或串行的异步操作时,这些回调函数会让你的代码难以维护。

ECMAScript 2015(又名 ECMAScript 6) 引入了一个原生的方法来解决这类问题:promises。如果你还不清楚 promise 是什么,可以阅读这篇文章《Javascript Promise概述》。jQuery 则提供了独具一格的另一种 promises,叫做 Deferred 对象。而且 Deferred 对象的引入时间要比 ECMAScript 引入 promise 早了好几年。在这篇文章里,我会介绍 Deferred 对象和它试图解决的问题是什么。

deferred对象是jQuery对Promises接口的实现。它是非同步操作的通用接口,可以被看作是一个等待完成的任务,开发者通过一些通过的接口对其进行设置。事实上,它扮演代理人(proxy)的角色,将那些非同步操作包装成具有某些统一特性的对象,典型例子就是Ajax操作、网页动画、web worker等等。

译者注:
1. Deferred是jQuery1.5新增的一个特性,很多人把它翻译成 “异步队列”,我觉得比较靠谱,毕竟和“延迟”没啥关系,不过这篇文章中我还采用deferred这个单词。

jQuery之Deferred对象详解,jquerydeferred

deferred对象是jQuery对Promises接口的实现。它是非同步操作的通用接口,可以被看作是一个等待完成的任务,开发者通过一些通过的接口对其进行设置。事实上,它扮演代理人(proxy)的角色,将那些非同步操作包装成具有某些统一特性的对象,典型例子就是Ajax操作、网页动画、web worker等等。

jQuery的所有Ajax操作函数,默认返回的就是一个deferred对象。

Promises是什么

由于JavaScript单线程的特点,如果某个操作耗时很长,其他操作就必需排队等待。为了避免整个程序失去响应,通常的解决方法是将那些排在后面的操作,写成“回调函数”(callback)的形式。这样做虽然可以解决问题,但是有一些显著缺点:

1.回调函数往往写成函数参数的形式,导致函数的输入和输出非常混乱,整个程序的可阅读性差;
2.回调函数往往只能指定一个,如果有多个操作,就需要改写回调函数。
3.整个程序的运行流程被打乱,除错和调试的难度都相应增加。

Promises就是为了解决这些问题而提出的,它的主要目的就是取代回调函数,成为非同步操作的解决方案。它的核心思想就是让非同步操作返回一个对象,其他操作都针对这个对象来完成。比如,假定ajax操作返回一个Promise对象。

复制代码 代码如下:

var promise = get('');

然后,Promise对象有一个then方法,可以用来指定回调函数。一旦非同步操作完成,就调用指定的回调函数。

复制代码 代码如下:

promise.then(function (content) {
  console.log(content)
})

可以将上面两段代码合并起来,这样程序的流程看得更清楚。

复制代码 代码如下:

get(' (content) {
  console.log(content)
})

在1.7版之前,jQuery的Ajax操作采用回调函数。

复制代码 代码如下:

$.ajax({
    url:"/echo/json/",
    success: function(response)
    {
       console.info(response.name);
    }
});

1.7版之后,Ajax操作直接返回Promise对象,这意味着可以用then方法指定回调函数。

复制代码 代码如下:

$.ajax({
    url: "/echo/json/",
}).then(function (response) {
    console.info(response.name);
});

deferred对象的方法

$.deferred()方法

作用是生成一个deferred对象。

复制代码 代码如下:

var deferred = $.deferred();

done() 和 fail()

这两个方法都用来绑定回调函数。done()指定非同步操作成功后的回调函数,fail()指定失败后的回调函数。

复制代码 代码如下:

var deferred = $.Deferred();
deferred.done(function(value) {
   alert(value);
});

它们返回的是原有的deferred对象,因此可以采用链式写法,在后面再链接别的方法(包括done和fail在内)。

resolve() 和 reject()

这两个方法用来改变deferred对象的状态。resolve()将状态改为非同步操作成功,reject()改为操作失败。

复制代码 代码如下:

var deferred = $.Deferred();
deferred.done(function(value) {
   alert(value);
});
deferred.resolve("hello world");

一旦调用resolve(),就会依次执行done()和then()方法指定的回调函数;一旦调用reject(),就会依次执行fail()和then()方法指定的回调函数。

state方法

该方法用来返回deferred对象目前的状态。

复制代码 代码如下:

var deferred = new $.Deferred();
deferred.state();  // "pending"
deferred.resolve();
deferred.state();  // "resolved"

该方法的返回值有三个:

1.pending:表示操作还没有完成。
2.resolved:表示操作成功。
3.rejected:表示操作失败。

notify() 和 progress()

progress()用来指定一个回调函数,当调用notify()方法时,该回调函数将执行。它的用意是提供一个接口,使得在非同步操作执行过程中,可以执行某些操作,比如定期返回进度条的进度。

复制代码 代码如下:

 var userProgress = $.Deferred();
    var $profileFields = $("input");
    var totalFields = $profileFields.length
    userProgress.progress(function (filledFields) {
        var pctComplete = (filledFields/totalFields)*100;
        $("#progress").html(pctComplete.toFixed(0));
    });
    userProgress.done(function () {
        $("#thanks").html("Thanks for completing your profile!").show();
    });
    $("input").on("change", function () {
        var filledFields = $profileFields.filter("[value!='']").length;
        userProgress.notify(filledFields);
        if (filledFields == totalFields) {
            userProgress.resolve();
        }
    });

then()

then()的作用也是指定回调函数,它可以接受三个参数,也就是三个回调函数。第一个参数是resolve时调用的回调函数,第二个参数是reject时调用的回调函数,第三个参数是progress()方法调用的回调函数。

复制代码 代码如下:

deferred.then( doneFilter [, failFilter ] [, progressFilter ] )

在jQuery 1.8之前,then()只是.done().fail()写法的语法糖,两种写法是等价的。在jQuery 1.8之后,then()返回一个新的deferred对象,而done()返回的是原有的deferred对象。如果then()指定的回调函数有返回值,该返回值会作为参数,传入后面的回调函数。

复制代码 代码如下:

var defer = jQuery.Deferred();
defer.done(function(a,b){
            return a * b;
}).done(function( result ) {
            console.log("result = " result);
}).then(function( a, b ) {
            return a * b;
}).done(function( result ) {
            console.log("result = " result);
}).then(function( a, b ) {
            return a * b;
}).done(function( result ) {
            console.log("result = " result);
});
defer.resolve( 2, 3 );

在jQuery 1.8版本之前,上面代码的结果是:

复制代码 代码如下:

result = 2
result = 2
result = 2

在jQuery 1.8版本之后,返回结果是

复制代码 代码如下:

result = 2
result = 6
result = NaN

这一点需要特别引起注意。

复制代码 代码如下:

$.ajax( url1, { dataType: "json" } )
.then(function( data ) {
    return $.ajax( url2, { data: { user: data.userId } } );
}).done(function( data ) {
  // 从url2获取的数据
});

上面代码最后那个done方法,处理的是从url2获取的数据,而不是从url1获取的数据。

利用then()会修改返回值这个特性,我们可以在调用其他回调函数之前,对前一步操作返回的值进行处理。

复制代码 代码如下:

var post = $.post("/echo/json/")
    .then(function(p){
        return p.firstName;
    });
post.done(function(r){ console.log(r); });

上面代码先使用then()方法,从返回的数据中取出所需要的字段(firstName),所以后面的操作就可以只处理这个字段了。

有时,Ajax操作返回json字符串里面有一个error属性,表示发生错误。这个时候,传统的方法只能是通过done()来判断是否发生错误。通过then()方法,可以让deferred对象调用fail()方法。

复制代码 代码如下:

var myDeferred = $.post('/echo/json/', {json:JSON.stringify({'error':true})})
    .then(function (response) {
            if (response.error) {
                return $.Deferred().reject(response);
            }
            return response;
        },function () {
            return $.Deferred().reject({error:true});
        }
    );
myDeferred.done(function (response) {
        $("#status").html("Success!");
    }).fail(function (response) {
        $("#status").html("An error occurred");
    });

always()

always()也是指定回调函数,不管是resolve或reject都要调用。

pipe方法

pipe方法接受一个函数作为参数,表示在调用then方法、done方法、fail方法、always方法指定的回调函数之前,先运行pipe方法指定的回调函数。它通常用来对服务器返回的数据做初步处理。

promise对象

大多数情况下,我们不想让用户从外部更改deferred对象的状态。这时,你可以在deferred对象的基础上,返回一个针对它的promise对象。我们可以把后者理解成,promise是deferred的只读版,或者更通俗地理解成promise是一个对将要完成的任务的承诺。

你可以通过promise对象,为原始的deferred对象添加回调函数,查询它的状态,但是无法改变它的状态,也就是说promise对象不允许你调用resolve和reject方法。

复制代码 代码如下:

function getPromise(){
    return $.Deferred().promise();
}
try{
    getPromise().resolve("a");
} catch(err) {
    console.log(err);
}

上面的代码会出错,显示TypeError {} 。

jQuery的ajax() 方法返回的就是一个promise对象。此外,Animation类操作也可以使用promise对象。

复制代码 代码如下:

var promise = $('div.alert').fadeIn().promise();

$.when()方法

$.when()接受多个deferred对象作为参数,当它们全部运行成功后,才调用resolved状态的回调函数,但只要其中有一个失败,就调用rejected状态的回调函数。它相当于将多个非同步操作,合并成一个。

复制代码 代码如下:

$.when(
    $.ajax( "/main.php" ),
    $.ajax( "/modules.php" ),
    $.ajax( "/lists.php" )
).then(successFunc, failureFunc);

上面代码表示,要等到三个ajax操作都结束以后,才执行then方法指定的回调函数。

when方法里面要执行多少个操作,回调函数就有多少个参数,对应前面每一个操作的返回结果。

复制代码 代码如下:

$.when(
    $.ajax( "/main.php" ),
    $.ajax( "/modules.php" ),
    $.ajax( "/lists.php" )
).then(function (resp1, resp2, resp3){
    console.log(resp1);
    console.log(resp2);
    console.log(resp3);
});

上面代码的回调函数有三个参数,resp1、resp2和resp3,依次对应前面三个ajax操作的返回结果。

when方法的另一个作用是,如果它的参数返回的不是一个Deferred或Promise对象,那么when方法的回调函数将 立即运行。

复制代码 代码如下:

$.when({testing: 123}).done(function (x){
  console.log(x.testing); // "123"
});

上面代码中指定的回调函数,将在when方法后面立即运行。

利用这个特点,我们可以写一个具有缓存效果的异步操作函数。也就是说,第一次调用这个函数的时候,将执行异步操作,后面再调用这个函数,将会返回缓存的结果。

复制代码 代码如下:

function maybeAsync( num ) {
  var dfd = $.Deferred();
  if ( num === 1 ) {
    setTimeout(function() {
      dfd.resolve( num );
    }, 100);
    return dfd.promise();
  }
  return num;
}
$.when(maybeAsync(1)).then(function (resp){
  $('#target').append('<p>' resp '</p>');
});
$.when(maybeAsync(0)).then(function (resp){
  $('#target').append( '<p>' resp '</p>');
});

上面代码表示,如果maybeAsync函数的参数为1,则执行异步操作,否则立即返回缓存的结果。

实例

wait方法

我们可以用deferred对象写一个wait方法,表示等待多少毫秒后再执行。

复制代码 代码如下:

$.wait = function(time) {
  return $.Deferred(function(dfd) {
    setTimeout(dfd.resolve, time);
  });
}

使用方法如下:

复制代码 代码如下:

$.wait(5000).then(function() {
  alert("Hello from the future!");
});

改写setTimeout方法

在上面的wait方法的基础上,还可以改写setTimeout方法,让其返回一个deferred对象。

复制代码 代码如下:

function doSomethingLater(fn, time) {
  var dfd = $.Deferred();
  setTimeout(function() {
    dfd.resolve(fn());
  }, time || 0);
  return dfd.promise();
}
var promise = doSomethingLater(function (){
  console.log( '已经延迟执行' );
}, 100);

自定义操作使用deferred接口

我们可以利用deferred接口,使得任意操作都可以用done()和fail()指定回调函数。

复制代码 代码如下:

Twitter = {
  search:function(query) {
    var dfr = $.Deferred();
    $.ajax({
     url:"",
     data:{q:query},
     dataType:'jsonp',
     success:dfr.resolve
    });
    return dfr.promise();
  }
}

使用方法如下:

复制代码 代码如下:

Twitter.search('intridea').then(function(data) {
  alert(data.results[0].text);
});

deferred对象的另一个优势是可以附加多个回调函数。

复制代码 代码如下:

function doSomething(arg) {
  var dfr = $.Deferred();
  setTimeout(function() {
    dfr.reject("Sorry, something went wrong.");
  });
  return dfr;
}
doSomething("uh oh").done(function() {
  alert("Won't happen, we're erroring here!");
}).fail(function(message) {
  alert(message)
});

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

Deferred对象简史

Deferred对象是在 jQuery 1.5 中引入的,该对象提供了一系列的方法,可以将多个回调函数注册进一个回调队列里、调用回调队列,以及将同步或异步函数执行结果的成功还是失败传递给对应的处理函数。从那以后,Deferred 对象就成了讨论的话题, 其中不乏批评意见,这些观点也一直在变化。一些典型的批评的观点如《你并没有理解 Promise 》和《论 Javascript 中的 Promise 以及 jQuery 是如何把它搞砸的》。

Promise 对象 是和 Deferred 对象一起作为 jQuery 对 Promise 的一种实现。在 jQuery1.x 和 2.x 版本中, Deferred 对象遵守的是《CommonJS Promises 提案》中的约定,而 ECMAScript 原生 promises 方法的建立基础《Promises/A 提案》澳门新萄京官方网站:jQuery的Deferred对象概述,5中动用deferred对象的代码。也是以这一提案书为根基衍生而来。所以就像我们一开始提到的,之所以 Deferred 对象没有遵循《Promises/A 提案》,是因为那时后者根本还没被构想出来。

由于 jQuery 扮演的先驱者的角色以及后向兼容性问题,jQuery1.x 和 2.x 里 promises 的使用方式和原生 Javascript 的用法并不一致。此外,由于 jQuery 自己在 promises 方面遵循了另外一套提案,这导致它无法兼容其他实现 promises 的库,比如 Q library。

不过即将到来的 jQuery 3 改进了 同原生 promises(在 ECMAScript2015 中实现)的互操作性。虽然为了向后兼容,Deferred 对象的主要方法之一(then())的方法签名仍然会有些不同,但行为方面它已经同 ECMAScript 2015 标准更加一致。

jQuery的所有Ajax操作函数,默认返回的就是一个deferred对象。

2. 这篇文章在jQuery1.5发布博客中提到,也是目前介绍deferred比较经典和深入的文章。鉴于目前中文资料比较少,特别翻译出来供大家学习参考。

jquery的实例对象与jquery的全局对象在用法上有什不同

{ this.id = sId; //函数内部的 this 是这个函数的实例 } 用 prototype对象,这个对象可以用 jQuery 的各种函数。 $('body a').click(function(  

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

jQuery中的回调函数

举一个例子来理解为什么我们需要用到 Deferred对象。使用 jQuery 时,经常会用到它的 ajax 方法执行异步的数据请求操作。我们不妨假设你在开发一个页面,它能够发送 ajax 请求给 GitHub API,目的是读取一个用户的 Repository 列表、定位到最近更新一个 Repository,然后找到第一个名为“README.md”的文件并获取该文件的内容。所以根据以上描述,每一个请求只有在前一步完成后才能开始。换言之,这些请求必须依次执行

上面的描述可以转换成伪代码如下(注意我用的并不是真正的 Github API):

JavaScript

var username = 'testuser'; var fileToSearch = 'README.md'; $.getJSON('' username '/repositories', function(repositories) { var lastUpdatedRepository = repositories[0].name; $.getJSON('' username '/repository/' lastUpdatedRepository '/files', function(files) { var README = null; for (var i = 0; i < files.length; i ) { if (files[i].name.indexOf(fileToSearch) >= 0) { README = files[i].path; break; } } $.getJSON('' username '/repository/' lastUpdatedRepository '/file/' README '/content', function(content) { console.log('The content of the file is: ' content); }); }); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var username = 'testuser';
var fileToSearch = 'README.md';
 
$.getJSON('https://api.github.com/user/' username '/repositories', function(repositories) {
  var lastUpdatedRepository = repositories[0].name;
 
$.getJSON('https://api.github.com/user/' username '/repository/' lastUpdatedRepository '/files', function(files) {
    var README = null;
 
for (var i = 0; i < files.length; i ) {
      if (files[i].name.indexOf(fileToSearch) >= 0) {
        README = files[i].path;
 
break;
      }
    }
 
$.getJSON('https://api.github.com/user/' username '/repository/' lastUpdatedRepository '/file/' README '/content', function(content) {
      console.log('The content of the file is: ' content);
    });
  });
});

如你所见,使用回调函数的话,我们需要反复嵌套来让 ajax 请求按照我们希望的顺序执行。当代码里出现许多嵌套的回调函数,或者有很多彼此独立但需要将它们同步的回调时,我们往往把这种情形称作“回调地狱 ( callback hell )“。

为了稍微改善一下,你可以从我创建的匿名函数中提取出命名函数。但这帮助并不大,因为我们还是在回调的地狱中,依旧面对着回调嵌套和同步的难题。这时是 DeferredPromise对象上场的时候了。

Promises是什么

3. 通篇采用意译的方式,如有不当还请大家提出。

jQuery对象问题

第一个是创建了一个validator对象,validator是这个对象的引用
第二个是在this[0]这个DOM对象上附上一个变量,名叫validator,这个对象的值就是第一行的那个validator,也就是刚刚创建的validator对象。
以后通过this[0]这个DOM对象就可以获取到validator这个对象了。  

deferred对象是jQuery对Promises接口的实现。它是非同步操作的通用接口,可以被看作是一个等待完成的任务...

 

Deferred和Promise对象

Deferred 对象可以被用来执行异步操作,例如 Ajax 请求和动画的实现。在 jQuery 中,Promise对象是只能由Deferred对象或 jQuery 对象创建。它拥有 Deferred 对象的一部分方法:always(),done(), fail(), state()then()。我们在下一节会讲到这些方法和其他细节。

如果你来自于原生 Javascript 的世界,你可能会对这两个对象的存在感到迷惑:为什么 jQuery 有两个对象(DeferredPromise)而原生JS 只有一个(Promise)? 在我著作的书《jQuery 实践(第三版)》里有一个类比,可以用来解释这个问题。

Deferred对象通常用在从异步操作返回结果的函数里(返回结果可能是 error,也可能为空)——即结果的生产者函数里。而返回结果后,你不想让读取结果的函数改变 Deferred 对象的状态(译者注:包括 Resolved 解析态,Rejected 拒绝态),这时就会用到 promise 对象——即 Promise 对象总在异步操作结果的消费者函数里被使用。

为了理清这个概念,我们假设你需要实现一个基于 promise 的timeout()函数(在本文稍后会展示这个例子的代码)。你的函数会等待指定的一段时间后返回(这里没有返回值),即一个生产者函数而这个函数的对应消费者们并不在乎操作的结果是成功(解析态 resolved)还是失败(拒绝态 rejected),而只关心他们需要在 Deferred 对象的操作成功、失败,或者收到进展通知后紧接着执行一些其他函数。此外,你还希望能确保消费者函数不会自行解析或拒绝 Deferred对象。为了达到这一目标,你必须在生产者函数timeout()中创建 Deferred 对象,并只返回它的 Promise 对象,而不是 Deferred对象本身。这样一来,除了timeout()函数之外就没有人能够调用到resolve()reject()进而改变 Deferred 对象的状态了。

在这个 StackOverflow 问题 里你可以了解到更多关于 jQuery 中 Deferred 和 Promise 对象的不同。

既然你已经了解里这两个对象,让我们来看一下它们都包含哪些方法。

由于JavaScript单线程的特点,如果某个操作耗时很长,其他操作就必需排队等待。为了避免整个程序失去响应,通常的解决方法是将那些排在后面的操作,写成“回调函数”(callback)的形式。这样做虽然可以解决问题,但是有一些显著缺点:

jQuery1.5中新增的Deferreds对象,可以将任务完成的处理方式与任务本身解耦合。这在JavaScript社区没什么新意,因为Mochikit和Dojo两个JS框架已经实现了这个特性很长一段时间了。但是随着Julian Aubourg对jQuery1.5中AJAX模块的重写,deferreds理所当然成为了内部的实现逻辑。使用deferreds对象,多个回调函数可以被绑定在任务完成时执行,甚至可以在任务完成后绑定这些回调函数。这些任务可以是异步的,也可以是同步的。

译者注:

Deferred对象的方法

Deferred对象相当灵活并提供了你可能需要的所有方法,你可以通过调用 jQuery.Deferred() 像下面一样创建它:

JavaScript

var deferred = jQuery.Deferred();

1
var deferred = jQuery.Deferred();

或者,使用 $作为 jQuery 的简写:

JavaScript

var deferred = $.Deferred();

1
var deferred = $.Deferred();

创建完 Deferred对象后,就可以使用它的一系列方法。处了已经被废弃的 removed 方法外,它们是:

  • always(callbacks[, callbacks, ..., callbacks]): 添加在该 Deferred 对象被解析或被拒绝时调用的处理函数
  • done(callbacks[, callbacks, ..., callbacks]): 添加在该 Deferred 对象被解析时调用的处理函数
  • fail(callbacks[, callbacks, ..., callbacks]): 添加在该 Deferred 对象被拒绝时调用的处理函数
  • notify([argument, ..., argument]):调用 Deferred 对象上的 progressCallbacks 处理函数并传递制定的参数
  • notifyWith(context[, argument, ..., argument]): 在制定的上下文中调用 progressCallbacks 处理函数并传递制定的参数。
  • progress(callbacks[, callbacks, ..., callbacks]): 添加在该 Deferred 对象产生进展通知时被调用的处理函数。
  • promise([target]): 返回 Deferred 对象的 promise 对象。
  • reject([argument, ..., argument]): 拒绝一个 Deferred 对象并以指定的参数调用所有的failCallbacks处理函数。
  • rejectWith(context[, argument, ..., argument]): 拒绝一个 Deferred 对象并在指定的上下文中以指定参数调用所有的failCallbacks处理函数。
  • resolve([argument, ..., argument]): 解析一个 Deferred 对象并以指定的参数调用所有的 doneCallbackswith 处理函数。
  • resolveWith(context[, argument, ..., argument]): 解析一个 Deferred 对象并在指定的上下文中以指定参数调用所有的doneCallbacks处理函数。
  • state(): 返回当前 Deferred 对象的状态。
  • then(resolvedCallback[, rejectedCallback[, progressCallback]]): 添加在该 Deferred 对象被解析、拒绝或收到进展通知时被调用的处理函数

从以上这写方法的描述中,我想突出强调一下 jQuery 文档和 ECMAScript 标准在术语上的不同。在 ECMAScript 中, 不论一个 promise 被完成 (fulfilled) 还是被拒绝 (rejected),我们都说它被解析 (resolved) 了。然而在 jQuery 的文档中,被解析这个词指的是 ECMAScript 标准中的完成 (fulfilled) 状态。

由于上面列出的方法太多, 这里无法一一详述。不过在下一节会有几个展示 DeferredPromise用法的示例。第一个例子中我们会利用Deferred 对象重写“ jQuery 的回调函数”这一节的代码。第二个例子里我会阐明之前讨论的生产者–消费者这个比喻。

1.回调函数往往写成函数参数的形式,导致函数的输入和输出非常混乱,整个程序的可阅读性差;
2.回调函数往往只能指定一个,如果有多个操作,就需要改写回调函数。
3.整个程序的运行流程被打乱,除错和调试的难度都相应增加。

更重要的是,deferreds已经作为$.ajax()的内部实现,所以你可以在调用AJAX时自动获取deferreds带来的遍历。比如我们可以这样绑定回调函数:

1. Deferred是jQuery1.5新增的一个特性,很多人把它翻译成 “异步队列”,我觉得比较靠谱,毕竟和“延迟”没啥关系,不过这篇文章中我还采用deferred这个单词。

利用 Deferred 依次执行 Ajax 请求

这一节我会利用Deferred对象和它提供的方法使“jQuery 的回调函数”这一节的代码更具有可读性。但在一头扎进代码之前,让我们先搞清楚一件事:在 Deferred 对象现有的方法中,我们需要的是哪些。

根据我们的需求及上文的方法列表,很明显我们既可以用 done()也可以通过 then()来处理操作成功的情况,考虑到很多人已经习惯了使用JS 的原生 Promise对象,这个示例里我会用 then()方法来实现。要注意 then()done()这两者之间的一个重要区别是 then()能够把接收到的值通过参数传递给后续的 then(),done(),fail()progress()调用。

所以最后我们的代码应该像下面这样:

JavaScript

var username = 'testuser'; var fileToSearch = 'README.md'; $.getJSON('' username '/repositories') .then(function(repositories) { return repositories[0].name; }) .then(function(lastUpdatedRepository) { return $.getJSON('' username '/repository/' lastUpdatedRepository '/files'); }) .then(function(files) { var README = null; for (var i = 0; i < files.length; i ) { if (files[i].name.indexOf(fileToSearch) >= 0) { README = files[i].path; break; } } return README; }) .then(function(README) { return $.getJSON('' username '/repository/' lastUpdatedRepository '/file/' README '/content'); }) .then(function(content) { console.log(content); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var username = 'testuser';
var fileToSearch = 'README.md';
 
$.getJSON('https://api.github.com/user/' username '/repositories')
  .then(function(repositories) {
    return repositories[0].name;
  })
  .then(function(lastUpdatedRepository) {
    return $.getJSON('https://api.github.com/user/' username '/repository/' lastUpdatedRepository '/files');
  })
  .then(function(files) {
    var README = null;
 
for (var i = 0; i < files.length; i ) {
      if (files[i].name.indexOf(fileToSearch) >= 0) {
        README = files[i].path;
 
break;
      }
    }
 
return README;
  })
  .then(function(README) {
    return $.getJSON('https://api.github.com/user/' username '/repository/' lastUpdatedRepository '/file/' README '/content');
  })
  .then(function(content) {
    console.log(content);
  });

如你所见,由于我们能够把整个操作拆分成同在一个缩进层级的各个步骤,这段代码的可读性已经显著提高了。

Promises就是为了解决这些问题而提出的,它的主要目的就是取代回调函数,成为非同步操作的解决方案。它的核心思想就是让非同步操作返回一个对象,其他操作都针对这个对象来完成。比如,假定ajax操作返回一个Promise对象。

复制代码 代码如下:

2. 这篇文章在jQuery1.5发布博客中提到,也是目前介绍deferred比较经典和深入的文章。鉴于目前中文资料比较少,特别翻译出来供大家学习参考。

创建一个基于 Promise 的 setTimeout 函数

你可能已经知道 setTimeout() 函数可以在延迟一个给定的时间后执行某个回调函数,只要你把时间和回调函数作为参数传给它。假设你想要在一秒钟后在控制台打印一条日志信息,你可以用它这样写:

JavaScript

setTimeout( function() { console.log('等待了1秒钟!'); }, 1000 );

1
2
3
4
5
6
setTimeout(
  function() {
    console.log('等待了1秒钟!');
  },
  1000
);

如你所见,setTimeout的第一个参数是要执行的回调函数,第二个参数是以毫秒为单位的等待时间。这个函数数年以来运转良好,但如果现在你需要在 Deferred对象的方法链中引入一段时间的延时该怎么做呢?

下面的代码展示了如何用 jQuery 提供的 Promise对象创建一个基于 promise 的 setTimeout(). 为了达到我们的目的,这里用到了 Deferred对象的 promise()方法。

代码如下:

JavaScript

function timeout(milliseconds) { //创建一个新Deferred var deferred = $.Deferred(); // 在指定的毫秒数之后解析Deferred对象 setTimeout(deferred.resolve, milliseconds); // 返回Deferred对象的Promise对象 return deferred.promise(); } timeout(1000).then(function() { console.log('等待了1秒钟!'); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function timeout(milliseconds) {
  //创建一个新Deferred
  var deferred = $.Deferred();
 
// 在指定的毫秒数之后解析Deferred对象
  setTimeout(deferred.resolve, milliseconds);
 
// 返回Deferred对象的Promise对象
  return deferred.promise();
}
 
timeout(1000).then(function() {
  console.log('等待了1秒钟!');
});

这段代码里定义了一个名为 timeout()的函数,它包裹在 JS 原生的 setTimeout()函数之外。

timeout()里, 创建了一个 Deferred对象来实现在延迟指定的毫秒数之后将 Deferred 对象解析(Resolve)的功能。这里 timeout()函数是值的生产者,因此它负责创建 Deferred对象并返回 Promise对象。这样一来调用者(消费者)就不能再随意解析或拒绝 Deferred 对象。事实上,调用者只能通过 done()fail()这样的方法来增加值返回时要执行的函数。

复制代码 代码如下:

// $.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已经结束
});

3. 通篇采用意译的方式,如有不当还请大家提出。

jQuery 1.x/2.x同 jQuery3 的区别

在第一个例子里,我们使用 Deferred对象来查找名字包含“README.md”的文件, 但并没有考虑文件找不到的情况。这种情形可以被看成是操作失败,而当操作失败时,我们可能需要中断调用链的执行并直接跳到程序结尾。很自然地,为了实现这个目的,我们应该在找不到文件时抛出一个异常,并用 fail()函数来捕获它,就像 Javascriopt 的 catch()的用法一样。

在遵守 Promises/A 和 Promises/A 的库里(例如jQuery 3.x),抛出的异常会被转换成一个拒绝操作 (rejection),进而通过 fail()方法添加的失败条件回调函数会被执行,且抛出的异常会作为参数传给这些函数。

在 jQuery 1.x 和 2.x中, 没有被捕获的异常会中断程序的执行。这两个版本允许抛出的异常向上冒泡,一般最终会到达 window.onerror。而如果没有定义异常的处理程序,异常信息就会被显示,同时程序也会停止运行。

为了更好的理解这一行为上的区别,让我们看一下从我书里摘出来的这一段代码:

JavaScript

var deferred = $.Deferred(); deferred .then(function() { throw new Error('一条错误信息'); }) .then( function() { console.log('第一个成功条件函数'); }, function() { console.log('第一个失败条件函数'); } ) .then( function() { console.log('第二个成功条件函数'); }, function() { console.log('第二个失败条件函数'); } ); deferred.resolve();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var deferred = $.Deferred();
deferred
  .then(function() {
    throw new Error('一条错误信息');
  })
  .then(
    function() {
      console.log('第一个成功条件函数');
    },
    function() {
      console.log('第一个失败条件函数');
    }
  )
  .then(
    function() {
      console.log('第二个成功条件函数');
    },
    function() {
      console.log('第二个失败条件函数');
    }
  );
 
deferred.resolve();

jQuery 3.x 中, 这段代码会在控制台输出“第一个失败条件函数” 和 “第二个成功条件函数”。原因就像我前面提到的,抛出异常后的状态会被转换成拒绝操作进而失败条件回调函数一定会被执行。此外,一旦异常被处理(在这个例子里被失败条件回调函数传给了第二个then()),后面的成功条件函数就会被执行(这里是第三个 then()里的成功条件函数)。

在 jQuery 1.x 和 2.x 中,除了第一个函数(抛出错误异常的那个)之外没有其他函数会被执行,所以你只会在控制台里看到“未处理的异常:一条错误信息。”

你可以到下面两个JSBin链接中查看它们的执行结果的不同:

  • jQuery 1.x/2.x
  • jQuery 3

为了更好的改善它同 ECMAScript2015 的兼容性,jQuery3.x 还给 DeferredPromise对象增加了一个叫做 catch()的新方法。它可以用来定义当 Deferred对象被拒绝或 Promise对象处于拒绝态时的处理函数。它的函数签名如下:

JavaScript

deferred.catch(rejectedCallback)

1
deferred.catch(rejectedCallback)

可以看出,这个方法不过是 then(null, rejectedCallback)的一个快捷方式罢了。

var promise = get('');

我们不再被限制到只有一个成功,失败或者完成的回调函数了。相反这些随时被添加的回调函数被放置在一个先进先出的队列中。
从上面例子看出,回调函数可以被附加到AJAX请求中(任何可观察的任务observable task),甚至在AJAX请求已经结束。对于代码的组织是很好的,我们再也不用写很长的回调函数了。这就像$.queue()遇到了pub/sub(发布订阅机制,一般用在基于事件处理的模型中).
更深入一些,想象这样一个场景,在一些并发的AJAX请求全部结束之后执行一个回调函数。我可以方便的通过jQuery的函数$.when()来完成:

 

总结

这篇文章里我介绍了 jQuery 实现的 promises。Promises 让我们能够摆脱那些用来同步异步函数的令人抓狂的技巧,同时避免我们陷入深层次的回调嵌套之中。

除了展示一些示例,我还介绍了 jQuery 3 在同原生 promises 互操作性上所做的改进。尽管我们强调了 jQuery 的老版本同ECMAScript2015 在 Promises 实现上有许多不同,Deferred对象仍然是你工具箱里一件强有力的工具。作为一个职业开发人员,当项目的复杂度增加时,你会发现它总能派上用场。

打赏支持我翻译更多好文章,谢谢!

打赏译者

然后,Promise对象有一个then方法,可以用来指定回调函数。一旦非同步操作完成,就调用指定的回调函数。

复制代码 代码如下:

 

打赏支持我翻译更多好文章,谢谢!

任选一种支付方式

澳门新萄京官方网站 1 澳门新萄京官方网站 2

1 赞 5 收藏 评论

复制代码 代码如下:

function doAjax() {
return $.get('foo.htm');
}

jQuery1.5中新增的Deferreds对象,可以将任务完成的处理方式与任务本身解耦合。这在JavaScript社区没什么新意,因为Mochikit和Dojo两个JS框架已经实现了这个特性很长一段时间了。但是随着Julian Aubourg对jQuery1.5中AJAX模块的重写,deferreds理所当然成为了内部的实现逻辑。使用deferreds对象,多个回调函数可以被绑定在任务完成时执行,甚至可以在任务完成后绑定这些回调函数。这些任务可以是异步的,也可以是同步的。

关于作者:HansDo

澳门新萄京官方网站 3

游走于Web前后端,一直在野路子上摸索着。对美术和数学有心无力(・-・*),尽其所能做一个生产者。 个人主页 · 我的文章 · 18 ·    

澳门新萄京官方网站 4

promise.then(function (content) {
  console.log(content)
})

function doMoreAjax() {
return $.get('bar.htm');
}

更重要的是,deferreds已经作为$.ajax()的内部实现,所以你可以在调用AJAX时自动获取deferreds带来的遍历。比如我们可以这样绑定回调函数:

可以将上面两段代码合并起来,这样程序的流程看得更清楚。

$.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.');
});

// $.get, 异步的AJAX请求

复制代码 代码如下:

在jsFiddle中打开示例

var req = $.get('foo.htm').success(function (response) {

get(' (content) {
  console.log(content)
})

上面的示例能够正常运行,这要归功于每个jQuery的AJAX方法返回值都包含一个promise函数,用来跟踪异步请求。Promise函数的返回值是deferred对象的一个只读视图。(The promise is a read-only view into the result of the task.)Deferreds通过检测对象中是否存在promise()函数来判断当前对象是否可观察。$.when()会等待所有的AJAX请求结束,然后调用通过 .then(), .fail()注册的回调函数(具体调用哪些回调函数取决于任务的结束状态)。这些回调函数会按照他们的注册顺序执行。

    // AJAX成功后的处理函数

在1.7版之前,jQuery的Ajax操作采用回调函数。

更好的是,$.when()接受函数或者函数的数组为参数(译者注:这点不大对,$.when接受一个或多个deferred对象,或者原生的JS对象。注意不能以函数数组为参数),这样你就可以随意组合这些异步任务。

}).error(function () {

复制代码 代码如下:

$.ajax()返回一个对象,这个对象关联一些deferred函数,比如promise(), then(), success(), error()。然而你不能操作原始的deferred对象,只有promise()函数(译者注:还记得刚才提到的promise是只读视图),以及可以检测deferred状态的isRejected() 以及isResolved()函数。

    // AJAX失败后处理函数

$.ajax({
    url:"/echo/json/",
    success: function(response)
    {
       console.info(response.name);
    }
});

但是为什么不返回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对象):

});

1.7版之后,Ajax操作直接返回Promise对象,这意味着可以用then方法指定回调函数。

复制代码 代码如下:

 

复制代码 代码如下:

.then( doneCallbacks, failedCallbacks )
.done( doneCallbacks )
.fail( failCallbacks )

// 这个函数有可能在AJAX结束前调用

$.ajax({
    url: "/echo/json/",
}).then(function (response) {
    console.info(response.name);
});

AJAX对象包含3个额外的方法,其中两个会映射到上面提到的方法。这些方法主要是为了兼容以前的代码:

doSomethingAwesome();

deferred对象的方法

复制代码 代码如下:

 

$.deferred()方法

// "success" 和 "error" 会分别映射到 "done" and "fail" 两个方法
.success( doneCallbacks )
.error( failCallbacks )

// 添加另外一个AJAX回调函数,此时AJAX或许已经结束,或许还没有结束

作用是生成一个deferred对象。

你也可以注册一个complete的回调函数,它会在请求结束后调用,而不管这个请求是成功或者失败。不像success或者error函数,complete函数其实是一个单独的deferred对象的done函数别名。这个在$.ajax()内部创建的deferred对象,会在AJAX结束后触发回调函数(resolve)。

// 由于$.ajax内置了deferred的支持,所以我们可以这样写

复制代码 代码如下:

复制代码 代码如下:

req.success(function (response) {

var deferred = $.deferred();

.complete( completeCallbacks )

    // 这个函数会在AJAX结束后被调用,或者立即被调用如果AJAX已经结束

澳门新萄京官方网站,done() 和 fail()

因此,下面的3个例子是等价的(在AJAX的上下文中,success看起来比done函数会舒服点,对么?)(译者注:其实是因为我们熟悉以前的AJAX调用方式,先入为主罢了,或者叫思维定势):

});

这两个方法都用来绑定回调函数。done()指定非同步操作成功后的回调函数,fail()指定失败后的回调函数。

复制代码 代码如下:

我们不再被限制到只有一个成功,失败或者完成的回调函数了。相反这些随时被添加的回调函数被放置在一个先进先出的队列中。

复制代码 代码如下:

$.get("/foo/").done( fn );
// 等价于:
$.get("/foo/").success( fn );
// 等价于:
$.get("/foo/", fn );

从上面例子看出,回调函数可以被附加到AJAX请求中(任何可观察的任务observable task),甚至在AJAX请求已经结束。对于代码的组织是很好的,我们再也不用写很长的回调函数了。这就像$.queue()遇到了pub/sub(发布订阅机制,一般用在基于事件处理的模型中).

var deferred = $.Deferred();
deferred.done(function(value) {
   alert(value);
});

创建自己的deferred对象(Creating your own Deferred)
我们知道$.ajax和$.when在内部实现了deferred接口,不过我们也可以手工创建deferred对象:

更深入一些,想象这样一个场景,在一些并发的AJAX请求全部结束之后执行一个回调函数。我可以方便的通过jQuery的函数$.when()来完成:

它们返回的是原有的deferred对象,因此可以采用链式写法,在后面再链接别的方法(包括done和fail在内)。

复制代码 代码如下:

function doAjax() {

resolve() 和 reject()

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的结果)
});

    return $.get('foo.htm');

这两个方法用来改变deferred对象的状态。resolve()将状态改为非同步操作成功,reject()改为操作失败。

在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()。因此下面的两端代码是等价的:

}

复制代码 代码如下:

复制代码 代码如下:

 

var deferred = $.Deferred();
deferred.done(function(value) {
   alert(value);
});
deferred.resolve("hello world");

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();
}

function doMoreAjax() {

一旦调用resolve(),就会依次执行done()和then()方法指定的回调函数;一旦调用reject(),就会依次执行fail()和then()方法指定的回调函数。

为自定义的deferred对象添加回调函数(Defer your Deferreds)
我们可以更进一步,为getData()和showDiv()分别注册回调函数,如同我们在$.then()中注册回调函数一样。(译者注:下面的段落内容重复,说的都是一个意思,就不翻译了,看代码吧)

    return $.get('bar.htm');

state方法

复制代码 代码如下:

}

该方法用来返回deferred对象目前的状态。

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!)

$.when(doAjax(), doMoreAjax()).then(function () {

var deferred = new $.Deferred();
deferred.state();  // "pending"
deferred.resolve();
deferred.state();  // "resolved"

复制代码 代码如下:

    console.log('I fire once BOTH ajax requests have completed!');

该方法的返回值有三个:

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请求失败
});

}).fail(function () {

1.pending:表示操作还没有完成。
2.resolved:表示操作成功。
3.rejected:表示操作失败。

saveContact()函数首先验证表单数据的有效性,然后把有效性状态保存在变量valid中。如果验证失败,直接deferred会被触发(把一个包含success状态码和错误信息的JS对象作为参数传递给回调函数)。如果验证通过,则向服务器提交数据,在AJAX成功完成后触发deferred对象。fail()会处理404, 500等可以阻止AJAX请求成功完成的HTTP状态码。
不可观察的任务(Non-observable Tasks)
Deferreds对于解耦任务与任务处理函数时非常有用,而不管是异步任务或者同步任务。一个任务可能会返回promise,但也可以返回字符串,对象或者其他类型。
在这个例子中,当“Lanch Application”链接被首次点击时,一个AJAX请求会发送到服务器并返回当前时间戳。然后这个时间戳会被保存到这个链接的data缓存中。当这个链接再次被点击时,只是简单的从缓存中取出这个时间戳返回,而不会发出AJAX请求。

    console.log('I fire if one or more requests failed.');

notify() 和 progress()

复制代码 代码如下:

});

progress()用来指定一个回调函数,当调用notify()方法时,该回调函数将执行。它的用意是提供一个接口,使得在非同步操作执行过程中,可以执行某些操作,比如定期返回进度条的进度。

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();
});

在jsFiddle中打开示例

复制代码 代码如下:

当$.when()发现它的第一个参数没有promise函数(因此不可观察),它就会创建一个新的deferred对象,触发deferred对象,并返回promise只读对象。因此,任意不可观察的任务也能传递到$.when()中。
需要注意的一个问题是,如果一个对象自身拥有promise函数,则这个对象将不能作为deferred对象。jQuery判断一个对象是否deferred,是通过查看它是否有promise函数来决定的,但是jQuery并不会检查这个promise是否真的返回一个可用的对象。因此下面的代码将会出错:

 

 var userProgress = $.Deferred();
    var $profileFields = $("input");
    var totalFields = $profileFields.length
    userProgress.progress(function (filledFields) {
        var pctComplete = (filledFields/totalFields)*100;
        $("#progress").html(pctComplete.toFixed(0));
    });
    userProgress.done(function () {
        $("#thanks").html("Thanks for completing your profile!").show();
    });
    $("input").on("change", function () {
        var filledFields = $profileFields.filter("[value!='']").length;
        userProgress.notify(filledFields);
        if (filledFields == totalFields) {
            userProgress.resolve();
        }
    });

复制代码 代码如下:

上面的示例能够正常运行,这要归功于每个jQuery的AJAX方法返回值都包含一个promise函数,用来跟踪异步请求。Promise函数的返回值是deferred对象的一个只读视图。(The promise is a read-only view into the result of the task.)Deferreds通过检测对象中是否存在promise()函数来判断当前对象是否可观察。$.when()会等待所有的AJAX请求结束,然后调用通过 .then(), .fail()注册的回调函数(具体调用哪些回调函数取决于任务的结束状态)。这些回调函数会按照他们的注册顺序执行。

then()

var obj = {
promise: function () {
// do something
}
};
$.when(obj).then(fn);

更好的是,$.when()接受函数或者函数的数组为参数(译者注:这点不大对,$.when接受一个或多个deferred对象,或者原生的JS对象。注意不能以函数数组为参数),这样你就可以随意组合这些异步任务。

then()的作用也是指定回调函数,它可以接受三个参数,也就是三个回调函数。第一个参数是resolve时调用的回调函数,第二个参数是reject时调用的回调函数,第三个参数是progress()方法调用的回调函数。

结论(Conclusion)
Deferreds提出了一种新的健壮的方式来处理异步任务。和传统的将代码组织到一个回调函数中不同,新的deferred对象允许我们在任何时候(甚至在任务结束后)绑定多个回调函数,而这些回调函数会以先进先出的方式被调用。这篇文章中的信息可能比较难以消化,不过一旦你掌握了deferred对象的使用,你会发现组织异步执行的代码将会非常容易。
本文章由三生石上原创,博客园首发,转载请注明出处

$.ajax()返回一个对象,这个对象关联一些deferred函数,比如promise(), then(), success(), error()。然而你不能操作原始的deferred对象,只有promise()函数(译者注:还记得刚才提到的promise是只读视图),以及可以检测deferred状态的isRejected() 以及isResolved()函数。

复制代码 代码如下:

您可能感兴趣的文章:

  • jQuery中deferred对象使用方法详解
  • 详解jQuery中的deferred对象的使用(一)
  • jQuery通过deferred对象管理ajax异步
  • jQuery的promise与deferred对象在异步回调中的作用
  • jQuery.deferred对象使用详解
  • 以jQuery中$.Deferred对象为例讲解promise对象是如何处理异步问题
  • jQuery的deferred对象详解
  • jQuery之Deferred对象详解
  • jquery基础教程之deferred对象使用方法
  • 利用jQuery的deferred对象实现异步按顺序加载JS文件
  • jQuery的deferred对象使用详解
  • 在jQuery1.5中使用deferred对象 着放大镜看Promise
  • jQuery的deferred对象使用详解

但是为什么不返回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/)

deferred.then( doneFilter [, failFilter ] [, progressFilter ] )

注册回调函数(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 );

在jQuery 1.8之前,then()只是.done().fail()写法的语法糖,两种写法是等价的。在jQuery 1.8之后,then()返回一个新的deferred对象,而done()返回的是原有的deferred对象。如果then()指定的回调函数有返回值,该返回值会作为参数,传入后面的回调函数。

创建自己的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中打开示例

 

var defer = jQuery.Deferred();
defer.done(function(a,b){
            return a * b;
}).done(function( result ) {
            console.log("result = " result);
}).then(function( a, b ) {
            return a * b;
}).done(function( result ) {
            console.log("result = " result);
}).then(function( a, b ) {
            return a * b;
}).done(function( result ) {
            console.log("result = " result);
});
defer.resolve( 2, 3 );

链式代码(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状态码。

在jQuery 1.8版本之前,上面代码的结果是:

不可观察的任务(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对象的使用,你会发现组织异步执行的代码将会非常容易。

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

result = 2
result = 2
result = 2

在jQuery 1.8版本之后,返回结果是

复制代码 代码如下:

result = 2
result = 6
result = NaN

这一点需要特别引起注意。

复制代码 代码如下:

$.ajax( url1, { dataType: "json" } )
.then(function( data ) {
    return $.ajax( url2, { data: { user: data.userId } } );
}).done(function( data ) {
  // 从url2获取的数据
});

上面代码最后那个done方法,处理的是从url2获取的数据,而不是从url1获取的数据。

利用then()会修改返回值这个特性,我们可以在调用其他回调函数之前,对前一步操作返回的值进行处理。

复制代码 代码如下:

var post = $.post("/echo/json/")
    .then(function(p){
        return p.firstName;
    });
post.done(function(r){ console.log(r); });

上面代码先使用then()方法,从返回的数据中取出所需要的字段(firstName),所以后面的操作就可以只处理这个字段了。

有时,Ajax操作返回json字符串里面有一个error属性,表示发生错误。这个时候,传统的方法只能是通过done()来判断是否发生错误。通过then()方法,可以让deferred对象调用fail()方法。

复制代码 代码如下:

var myDeferred = $.post('/echo/json/', {json:JSON.stringify({'error':true})})
    .then(function (response) {
            if (response.error) {
                return $.Deferred().reject(response);
            }
            return response;
        },function () {
            return $.Deferred().reject({error:true});
        }
    );
myDeferred.done(function (response) {
        $("#status").html("Success!");
    }).fail(function (response) {
        $("#status").html("An error occurred");
    });

always()

always()也是指定回调函数,不管是resolve或reject都要调用。

pipe方法

pipe方法接受一个函数作为参数,表示在调用then方法、done方法、fail方法、always方法指定的回调函数之前,先运行pipe方法指定的回调函数。它通常用来对服务器返回的数据做初步处理。

promise对象

大多数情况下,我们不想让用户从外部更改deferred对象的状态。这时,你可以在deferred对象的基础上,返回一个针对它的promise对象。我们可以把后者理解成,promise是deferred的只读版,或者更通俗地理解成promise是一个对将要完成的任务的承诺。

你可以通过promise对象,为原始的deferred对象添加回调函数,查询它的状态,但是无法改变它的状态,也就是说promise对象不允许你调用resolve和reject方法。

复制代码 代码如下:

function getPromise(){
    return $.Deferred().promise();
}
try{
    getPromise().resolve("a");
} catch(err) {
    console.log(err);
}

上面的代码会出错,显示TypeError {} 。

jQuery的ajax() 方法返回的就是一个promise对象。此外,Animation类操作也可以使用promise对象。

复制代码 代码如下:

var promise = $('div.alert').fadeIn().promise();

$.when()方法

$.when()接受多个deferred对象作为参数,当它们全部运行成功后,才调用resolved状态的回调函数,但只要其中有一个失败,就调用rejected状态的回调函数。它相当于将多个非同步操作,合并成一个。

复制代码 代码如下:

$.when(
    $.ajax( "/main.php" ),
    $.ajax( "/modules.php" ),
    $.ajax( "/lists.php" )
).then(successFunc, failureFunc);

上面代码表示,要等到三个ajax操作都结束以后,才执行then方法指定的回调函数。

when方法里面要执行多少个操作,回调函数就有多少个参数,对应前面每一个操作的返回结果。

复制代码 代码如下:

$.when(
    $.ajax( "/main.php" ),
    $.ajax( "/modules.php" ),
    $.ajax( "/lists.php" )
).then(function (resp1, resp2, resp3){
    console.log(resp1);
    console.log(resp2);
    console.log(resp3);
});

上面代码的回调函数有三个参数,resp1、resp2和resp3,依次对应前面三个ajax操作的返回结果。

when方法的另一个作用是,如果它的参数返回的不是一个Deferred或Promise对象,那么when方法的回调函数将 立即运行。

复制代码 代码如下:

$.when({testing: 123}).done(function (x){
  console.log(x.testing); // "123"
});

上面代码中指定的回调函数,将在when方法后面立即运行。

利用这个特点,我们可以写一个具有缓存效果的异步操作函数。也就是说,第一次调用这个函数的时候,将执行异步操作,后面再调用这个函数,将会返回缓存的结果。

复制代码 代码如下:

function maybeAsync( num ) {
  var dfd = $.Deferred();
  if ( num === 1 ) {
    setTimeout(function() {
      dfd.resolve( num );
    }, 100);
    return dfd.promise();
  }
  return num;
}
$.when(maybeAsync(1)).then(function (resp){
  $('#target').append('<p>' resp '</p>');
});
$.when(maybeAsync(0)).then(function (resp){
  $('#target').append( '<p>' resp '</p>');
});

上面代码表示,如果maybeAsync函数的参数为1,则执行异步操作,否则立即返回缓存的结果。

实例

wait方法

我们可以用deferred对象写一个wait方法,表示等待多少毫秒后再执行。

复制代码 代码如下:

$.wait = function(time) {
  return $.Deferred(function(dfd) {
    setTimeout(dfd.resolve, time);
  });
}

使用方法如下:

复制代码 代码如下:

$.wait(5000).then(function() {
  alert("Hello from the future!");
});

改写setTimeout方法

在上面的wait方法的基础上,还可以改写setTimeout方法,让其返回一个deferred对象。

复制代码 代码如下:

function doSomethingLater(fn, time) {
  var dfd = $.Deferred();
  setTimeout(function() {
    dfd.resolve(fn());
  }, time || 0);
  return dfd.promise();
}
var promise = doSomethingLater(function (){
  console.log( '已经延迟执行' );
}, 100);

自定义操作使用deferred接口

我们可以利用deferred接口,使得任意操作都可以用done()和fail()指定回调函数。

复制代码 代码如下:

Twitter = {
  search:function(query) {
    var dfr = $.Deferred();
    $.ajax({
     url:"",
     data:{q:query},
     dataType:'jsonp',
     success:dfr.resolve
    });
    return dfr.promise();
  }
}

使用方法如下:

复制代码 代码如下:

Twitter.search('intridea').then(function(data) {
  alert(data.results[0].text);
});

deferred对象的另一个优势是可以附加多个回调函数。

复制代码 代码如下:

function doSomething(arg) {
  var dfr = $.Deferred();
  setTimeout(function() {
    dfr.reject("Sorry, something went wrong.");
  });
  return dfr;
}
doSomething("uh oh").done(function() {
  alert("Won't happen, we're erroring here!");
}).fail(function(message) {
  alert(message)
});

您可能感兴趣的文章:

  • jQuery的deferred对象使用详解
  • jQuery Deferred和Promise创建响应式应用程序详细介绍
  • 利用jQuery的deferred对象实现异步按顺序加载JS文件
  • jQuery源码分析-05异步队列 Deferred 使用介绍
  • jQuery $.extend()用法总结
  • jQuery插件开发的两种方法及$.fn.extend的详解
  • jQuery.extend 函数详解
  • 原生js实现复制对象、扩展对象 类似jquery中的extend()方法
  • jQuery.extend()的实现方式详解及实例
  • jQuery中的deferred对象和extend方法详解

本文由澳门新萄京官方网站发布于澳门新萄京赌场网址,转载请注明出处:澳门新萄京官方网站:jQuery的Deferred对象概述,

关键词: