之前的文章"javascript异步代码的回调地狱"中提到了编写AJAX代码经常遇到的3个问题,现在我们看下如何利用then()解决第2个问题:如果AJAX请求之间存在依赖关系,我们的代码就会形成Pyramid of Doom(金字塔厄运)。比如我们要完成这样一件事:有4个供Ajax访问的url地址,需要先Ajax访问第1个,在第1个访问完成后,用拿到的返回数据作为参数再访问第2个,第2个访问完成后再第3个...以此到4个全部访问完成。按照这样的写法,似乎会变成这样:
1. $.ajax({
2. url: url1,
3. function(data){
4. $.ajax({
5. url: url2,
6. data: data,
7. function(data){
8. $.ajax({
9. //...
10. });
11. }
12. });
13. }
14. });
1.Deferred.then()相当于Deferred.done()、Deferred.fail()、Deferred.progress()的合体,可以同时注册3个状态下的回调函数。
1. function success(data)
2. {
3. "success data = " + data);
4. }
5.
6. function fail(data)
7. {
8. "fail data = " + data);
9. }
10.
11. function progress(data)
12. {
13. "progress data = " + data);
14. }
15.
16. var deferred = $.Deferred();
17.
18. // 一起注册回调
19. deferred.then(success, fail, progress);
20.
21. // 分别注册回调
22. deferred.done(success);
23. deferred.fail(fail);
24. deferred.progress(progress);
25.
26. deferred.notify("10%");
27. deferred.resolve("ok");
当然我们也可以像done()一样,多次调用then()注册回调函数。then()虽然可以这么使用,但是实际开发中一般不这么用,因为没有啥必要。JQuery1.8之前,这就是then()方法的作用。
2.Deferred.then()解决多个异步操作之间有依赖的问题,这才是then()真正有意义的场景。JQuery1.8之后,then()取代了过时的pipe()方法。这种场景下,我们需要使用Deferred.then()返回的新Promise对象。上面的第一种使用方式,我们忽略了Deferred.then()的返回值。
1. var deferred = $.Deferred();
2.
3. // 使用then()注册一个resolved状态的回调函数,并返回一个过滤后的promise
4. // 返回的filtered已经不是原来的Deferred或者Promise对象了
5. var filtered = deferred.then(function( value ) {
6. "trigger Deferred filter.value="+value);//5
7. return value * 2;
8. });
9.
10. // 用过滤后的Promise再次注册回调函数
11. filtered.done(function( value ) {
12. "filtered value=" + value);//10
13. });
14.
15. deferred.resolve( 5 );
我们用deferred.then()注册了一个完成状态下的回调函数,这个回调函数得到的值是5;之后用filtered这个新的Promise注册回调函数,这个回调函数中得到的值是10(第一个回调函数的返回结果)。现在我们看下JQuery官方对then的解释:
These filter functions can return a new value to be passed along to the promise's .done() or .fail() callbacks, or they can return another observable object (Deferred, Promise, etc) which will pass its resolved / rejected status and values to the promise's callbacks. If the filter function used is null, or not specified, the promise will be resolved or rejected with the same values as the original.
我们知道deferred.resolve()、deferred.reject()、deferred.notify()可以指定参数值,这个参数会传递给相应状态下的回调函数。如果我们使用的是done()、fail()、progress()注册的回调函数,那么某个状态下的所有回调函数得到的都是相同参数。但是如果我们使用了then()注册回调函数,那么第一回调函数的返回值将作为第二个回调函数的参数,同样的第二个函数的返回值是第三个回调函数的参数。可以对比下面的2段代码,体会下done()和then的差别。
1. var deferred = $.Deferred();
2.
3. // then()返回的是一个新Promise对象
4. //then注册的回调函数的返回值将作为这个新Promise的参数
5. var then_ret = deferred.then(function(data){
6. "data="+data);//5
7. return 2 * data;
8. });
9. alert(then_ret == deferred);//false
10.
11. then_ret.done(function(data){
12. "data="+data);//10
13. });
14.
15. deferred.resolve( 5 );
同样地,Deferred.then也能够实现rejected和pending状态的回调函数过滤。
1. var defer = $.Deferred();
2. var filtered = defer.then( null, function( value ) {
3. return value * 3;
4. });
5.
6. defer.reject( 6 );
7.
8. filtered.fail(function( value ) {
9. "Value is ( 3*6 = ) 18: " + value );
10. });
下面这段代码可以实现chain tasks,解决异步操作中回调难的问题。
1. var defered = $.Deferred();
2.
3. var promise1 = defered.then(function(data){
4. //
5. return data+="1";
6. });
7.
8. var promise2 = promise1.then(function(data){
9. //1
10. return data+="2";
11. });
12.
13. var promise3 = promise2.then(function(data){
14. //12
15. return data+="3";
16. });
17.
18. promise3.done(function(data){
19. //123
20. });
21.
22. defered.resolve("");
正是由于then()这个特性,我们就可以上面复杂的AJAX嵌套改成如下形式:
1. var promise1 = $.ajax(url1);
2. var promise2 = promise1.then(function(data){
3. return $.ajax(url2, { "data": data });
4. });
5. var promise3 = promise2.then(function(data){
6. return $.ajax(url3, { "data": data });
7. });
8. promise3.done(function(data){
9. // data retrieved from url3
10. });