盒子
文章目录
  1. 探索jQuery中的Deferred和Promise

探索jQuery中的Deferred和Promise

探索jQuery中的Deferred和Promise

原来对deferred跟promise的理解不是很清晰,处理ajax尤其是jsonp请求时还是习惯使用传统的success和failure,对于许多场景这种处理不是很理想,通过读Exploring Deferred and Promise in JQuery比较清晰的理解了一下在jquery中的应用模式,自译了一下这篇文章,不足之处欢迎指正。

jQuery从1.5版本引入deferred对象,deferred对象是基于promise上的一种实现。为了全面了解deferred对象,一个聪明的做法是彻底理解promise是做什么的。

在我们的生活中很多时候并不能确定许多事情的结果。但是我们仍然要根据一个预计的结果作出对未来的规划。我们确信的是我们想要一个结果或者说我们给”结果”一个漂亮的包装并称之为”promise”(承诺)。

那么,究竟什么是一个”promise”呢。说白了就是一个一个对象,这个对象返回自一个基于你可以确信的未来一系列操作的方法。

让我们讲一个真实世界的情况。

你决定参加一个X公司的工作面试(这可以理解为一个你想要展示的function)。面试必然有一个结果,你获得了这个工作机会或者没有。但是你需要做一个计划无论面试的结果是怎么样的。并且你需要现在做这个计划,你不能在面试之后再计划。比如说,你可能希望在面试成功后来一个旅行,或者办一个party庆祝你面试成功。如果你没通过,你可能必须要买一些书去准备申请Y公司。

如果把这这个计划程序化,我们最终会写出这样一个东西:

1
2
3
4
5
6
7
8
9
10
candidate.attendInterview().then(
successFunction(){
//Book tickets.
//Order a crate of beer for a party!
},
failureFunction(){
//Buy some more books to brush up
//Apply to company Y
}
);

现在比较有趣的是尽管你不知道参加面试这个function的实际结果,你仍可以为它关联上一个可能的结果并且让这些functions响应当’attendInterview’funciton完成时。这个过程之所变的可能是因为’attendInterview’方法会返回一个’promise’对象。这个模式一个有趣的部分是promise解决方案的概念。Promise解决方案被认为发生在当任务返回真实完成的promise时。所以,当你返回attendInterview方法的promise对象时,这个promise是没有被resolve的。只有当操作的结果能被获得时,即当最终的面试结果在一段时间后被通知后,这个promise可以说被resolve了。基于这个方案,基于方案的记过,successFunction或者failureFunciton会被唤起。

promises的概念因为以下三个原因变得很重要:

1.它帮你将逻辑上对未来的处理从真实的事件调用中剥离。

2.你可以添加任意数量的处理。

3.如果你添加一个处理到已经解决的promise,适当的(成功或失败)方法或被立即触发。

更能清晰体现promise优势的场景是当被用在ajax请求中时,因为一个ajax请求的结果将在未来某个时间点被获取,ajax请求的结果在被完成前并不能被确定而完成这个过程需要相当长的一个时间。

jQuery在1.5版本中引入deferred对象来处理这个场景。Deferred对象实际上一个promise接口的实现。它提供了所以promise的特性,除此之外,它允许你创建一个新的deferred对象,给它们添加一些未来的处理并且用程序化的方式处理它们。

让我们从一个ajax请求来理解jQuery deferred对象。

在jQuery的1.5版本之前,为了添加一个成功或者一个失败处理到一个ajax请求,当定义一个ajax请求时会声明一个定义成功的回调函数。尽管比起其包裹起来的xmlhttprequest处理方便了许多,当这个方案仍然有一个缺点,必须定义一个方法会在未来被唤醒(成功或者失败)作为ajax方法的一部分。更进一步来说,不能为一个ajax请求添加多个成功或者失败的处理。

这是我们在1.5版本前的一个请求

1
2
3
4
5
6
7
8
9
$.ajax({
url: "test.html",
success: function(){
alert("ajax request succesful");
},
failure: function(){
alert("ajax request failed");
}
});

正如上面的code所展示的,它可以被看作你绑定了一个具体的success方法当制定这个ajax请求时。此外success和failure各只有一个可用的回调。

到了1.5版本,新引入的deferred翻天覆地般的改变了这个模式。它应该说是一个革新的变化,因为它不仅为ajax方法添加了更多的便利,同时给予了整个框架更多的实用功能。我们应该看看它是如何做到的。

我们现在用大于1.5版本的jQuery调用一个我们之前看到的一样的ajax请求。

1
2
3
4
5
6
7
8
9
10
11
12
var myRequest = $.ajax({  url: "test.html" })
.done(function(data){
alert('ajax request was successful');
})
.fail(function(data){
alert('ajax request failed');
});

//After a few more lines of code
myRequest.done(function(data){
$('div.myclass').html('Ajax Response ' + data);
});

正如我们看到,它远远好于我们之前看到的那种ajax请求方式。done和fail方法被用来给ajax的返回对象注册回调。这个ajax请求回复了一个实现了promise接口的deferred对象。因为它是一个promise,你可以添加多个处理给它以至当请求完成时,这些未来的处理会被唤起。

不仅仅如此,正如你在这个例子中所见,我们可以添加一个新的success处理(在done方法中的参数)在一个之后时间点。如果,运行到了这行代码,这个ajax请求没有被完成,这个方法会被放入队列并且会在一个的时间点会唤起。当这个请求已经完成,在成功的情况下这个方法会被立即执行。同样,你可以添加多个方法到失败队列中。

这个方法给予了我们在代码编写中更多的可变性并且使我们的代码结构更加清晰。Defered对象还拥有许多别的方法。其中一个很有意思的是when()方法。这个方法有很强的吸引力因为它允许你合并多个deferred对象,然后设置多个未来处理在所有的对象被成功响应或被拒绝之后。

这个方法开创了一个门用来创建依赖多个源输入的多个接口,并且这些接口需要被同时实现。举例来说,一个特定的div包涵着的信息基于一个用户的选项。这个用户的选项引导了两个不同ajax请求的执行。如果你的数据是这一类的,那么这个信息只有当来自两个请求的数据同时可见时才会有意义,对于这种场景使用when方法是一个最佳的候选。

在下面这种例子中,你可以发布一个向两个不同的url的请求,只有当两个url的数据都被获取后,被写在done中的方法才会被调用。

1
2
3
4
$.when($.ajax("mypage1.html"), $.ajax("mypage2.html")).done(function(a1,  a2){
$('div.page1details').html(a1[0]);
$('div.page1details').html(a2[0]);
});

很明显在when方法内部有两个方法调用。你可以随你所愿拥有任意数量的方法。唯一的标准是方法调用返回的对象必须是一个promise或者deferred。如果是一个promise,之后很好处理。如果是一个deferred,promise方法将被唤起同时promise对象将从deferred对象中获取。一个父deferred对象被创建,这个父对象保存着所有在when方法中定义的方法中的deferred对象。一旦所有在when方法内被声明的方法被resolved,这个done方法将被唤起。但是如果任意一个定义在when中的方法失败,这个失败的回调被立即唤起而不是等待其它方法的resolution或者rejection。

你可以简单的用done和fail方法为一个deferred对象注册将来的回调。另一个方法是你可以达到同样效果的是使用then方法。如果你使用then方法-then方法组合,它可以使代码变得更容易在语法上理解,因为它体现了一种语句上的特性。让我们看一个用when-then的例子,同时我们也可以看下如何在一个方法上注册多个回调

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
$(function(){

function fun1(data1, data2){
console.log("fun1 : " + data1[0].query + " " +
data1[0].results.length);
console.log("fun1 : " + data2[0].query + " " +
data2[0].results.length);
}

function fun2(data1, data2){
console.log("fun1 : " + data1[0].query + " " +
data1[0].results.length);
console.log("fun1 : " + data2[0].query + " " +
data2[0].results.length); }

function fun3(data){
console.log("fun3 called upon faliure");
}

function fun4(data){
console.log("fun4 called upon faliure");
}

var successFunctions = [fun1, fun2];
var failureFunctions = [fun3, fun4];

$.when(
$.ajax("http://search.twitter.com/search.json", {
data: {
q: 'jquery'
},
dataType: 'jsonp'
})
,
$.ajax("http://search.twitter.com/search.json", {
data: {
q: 'blogger'
},
dataType: 'jsonp'
})
).then(successFunctions,failureFunctions);

});

在上面这个例子中,我创建了四个方法,其中两个在success情况下被唤起另两个在failure下被唤起。正如你所见,不同于穿一个单独的方法在做参数给then(),我传给success一个数组里的方法,failure的回调也是如次。这里的另一点即使我们拥有两个ajax请求在when方法中,success和failure可以接受这两种参数。每一种参数将包涵着被相应ajax调用返回的jqXhr对象。所以,上面这个例子示范了如何使用多个回调和如何使用来自一个json ajax请求的数据。

你同时需要注意的是因为这个ajax方法用来创建一个jsonp方法,我已经分别用data1[0]和data2[0]引用了这个json对象在success的回调中。data1和data2对象实际上是数组形式[JSONObject,”succes”,jqXHR].在请求是一般ajax而非jsonp请求时,你可以使用jqXHR方法获取responseText。

到现在我们已经看过被ajax调用返回的deferred对象例子。尽管大量的被使用于ajax,deferred对象也可以被使用于其它地方。一个例子是想要执行一个回调在一系列动画结束之后,你可以组合所有动画在一个when方法中,然后使用一个deferred对象,当使用then()方法时你可以轻松的唤起回调。

这里有个问题,动画并不会返回一个deferred对象。它们总是返回一个简单的jQuery对象。这里需要deferred创造器来拯救。deferred创造器可以被用来创造新的的deferred对象并且你可以包裹你自己的代码放入一个deferred对象,返回这个deferred对象来替换你的jQuery对象。

让我们看一个这样的例子,在接下来的代码中,我们进行发布一个ajax请求,重新制定一个div的尺寸。在这些任务完成后,我们将在div中展示ajax请求到的内容。

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
30
$(function(){

function successFunction(data){
console.log("successfunction");
$('div.animateMe').html('Results : ' + data[0].results.length);
}

function animateDiv(){
//As per the documentation, the argument that is passed
//to the constructor is the newly created deferred object
var dfd = $.Deferred(function(dfd){
$('div.animateMe').animate({height:'200px'},2000,dfd.resolve);
});

return dfd;
}

function failureFunction(){
console.log("failureFunction");
}

$.when(
$.ajax("http://search.twitter.com/search.json", {
data: {
q: 'jquery'
},
dataType: 'jsonp'
}),animateDiv()
).then(successFunction,failureFunction);
});

这里例子十分简单,因为它仅仅在等待ajax请求的同时在resolve deferred对象前完成动画。这里的最有趣的是这个例子在animateDiv中创建了一个deferred对象。在这个方法中,我们先创建了一个新的deferred对象。这个deferred对象的创造器使用一个方法作为参数在创造器返回前被唤起。这个方法被当作新的deferred对象来传参。在这个方法中,我们做了一个动画并且挡动画一旦完成,我们指示框架resolve这个diferred对象通过传不同deferred对象的resolve方法作为参数。在下一行,我们只需简单的返回一个新的deferred object。

在上面的例子中,你可能听到我们使用了一个’resolve’方法。这个方法允许你明确的resolve一个deferred对象。尽管程序化的resolve一个deferred对象的能力被希望使用在非ajax请求中,但这与ajax请求中的并不相同。因为一个ajax请求只有在接收到服务器的响应后才会被resolved,所以一个ajax请求的resolution发生在ajax方法的内部,并不能用直接提供给程序员。

对于一个非ajax的请求,例如上面所示,程序员可以resolve这个deferred对象基于特定的需求。因为这个deferred对象拥有resolve方法和一系列的其它方法,ajax请求实际上返回了一个promise。让你只唤起promise接口的函数产生的对象以此来阻止在ajax请求实际完成前一个程序的resolve调用。

但你正在创建一个非ajax deferred对象,有另一个方法-reject,用来指明一个failure并唤起failure函数的队列。毫无疑问,deferred对象有一个小而实用的api。

总结:

1.jQuery中的deferred对象是一种对promise说明的实现,这意味着你可以用promise替换任何deferred。

2.将来处理可以被添加到promise对象。

3.将来处理会被唤起基于被唤起的方法的resolution(success)或者rejection(failure),你可以添加多个将来处理对任意deferred对象。

4.将来处理可以接收用来处理deferred对象的信息的参数。

5.非ajax对象可以用deferred创造器包裹在一个deferred对象中。

6.jQuery when方法可以用来组合一系列的deferred对象并给他们添加未来处理。

7.所有的deferred对象被resolved后when方法会一次性唤起所有的success方法

8.任意一个deferred对象失败后不用考虑其他deferred对象when方法会唤起所有的failure方法。

原文出处:jQuery : Exploring Deferred and Promise