1. 程式人生 > >jQuery.Deferred物件

jQuery.Deferred物件

文章出處:https://www.cnblogs.com/zouyanzhi/p/5283059.html

一、前言

jQuery1.5之前,如果需要多次Ajax操作,我們一般會使用下面的兩種方式:

1).序列呼叫Ajax

$.ajax({ success: function() { 
    $.ajax({ success: function() {
        $.ajax({ //callbacks... 
                }); 
    }); 
}); 

這種方式程式碼可讀性差,效率低,晦澀難懂,除錯和排錯的複雜度大。

2).並行呼叫Ajax

var
promises = []; $.ajax({ success: function() { promises.push('resolved'); check(); } }); $.ajax({ success: function() { promises.push('resolved'); check(); } }); $.ajax({ success: function() { promises.push('resolved'); check(); } });
var check = function() { //checks for all 3 values in the promises array }

這種方式對於callbacks函式呼叫來說已經很不錯了,並行取得資料,可讀性良好。缺點就是程式碼冗長,可擴充套件性差,除錯和排錯的複雜度高。

jQuery1.5之後,增加了deferred物件。因此可以用下面這種方式實現和上面同樣的需求。

1)Promise

var address = $.ajax({}); 
var tweets = $.ajax({}); 
var facebook = $.ajax({}); 
render_side_bar 
= function(address, tweets, facebook){ //render sidebar } render_no_side_bar = function () { } $.when(address, tweets, facebook).then(render_side_bar, render_no_side_bar)

可以看出,程式碼可讀性良好,可擴充套件性高,並且大大降低了除錯和排錯的複雜度。

那麼問題來了,promises和deferred物件究竟是個什麼玩意呢?

二、詳解

1.什麼是deferred物件?

deferred物件即延遲物件,它是jQuery 1.5版本引入的一種回撥函式的解決方案,代表了將要完成的某種操作,並且提供了一些方法,幫助使用者使用。

deferred物件是對Promises介面的實現。jQuery 1.5版本以及之後所有的Ajax返回的jqXHR物件就是一個deferred物件。

2.deferred物件的幾大好處

2.1.為同一操作指定多個回撥函式

deferred物件的好處之一,就是它允許你為一個操作新增多個回撥函式,這在傳統的ajax中是無法實現的。

$.ajax("test.html")
  .done(function(){ alert("first success callback!");} )
  .fail(function(){ alert("there is an error!"); } )
  .done(function(){ alert("second success callback!");} );

2.2.為多個操作指定同一個回撥函式

deferred物件的好處之二,就是它允許你為多個操作指定同一個回撥函式,這在傳統的ajax中也是無法實現的。

$.when($.ajax({}), $.ajax({}))
  .done(function(){ alert("success!"); })
  .fail(function(){ alert("error!"); });

2.3.非Ajax操作的回撥函式

deferred物件的好處之三,就是它不再拘泥於ajax操作,任意的操作(ajax操作or本地操作/非同步操作or同步操作)都可以使用deferred物件,指定回撥函式。

一個很典型的耗時操作

var dfd = $.Deferred(); // create a deferred object
  var wait = function(dtd){
    var tasks = function(){
      alert("over!");
      dtd.resolve(); // change the state of the deferred object from pending to resolved
    };
    setTimeout(tasks,50000);
    return dtd;
  };
$.when(wait(dtd))
  .done(function(){ alert("success!"); })
  .fail(function(){ alert("error!"); });

2.4.鏈式呼叫

jQuery中傳統的ajax操作是這樣的:

$.ajax({
    url: "",
   success: function(){
    alert("success!");
   },
   error:function(){
    alert("error!");
   }
});

其中success指定ajax操作成功後的回撥函式,error指定ajax操作失敗後的回撥函式。jQuery1.5版本之前,Ajax操作返回的是一個XMLHTTPRequest物件,不支援鏈式操作。1.5版本開始,ajax操作返回的是jqXHR物件,這是一個deferred物件,而deferred物件一個顯著的好處就是可以進行鏈式操作,因為deferred物件的所有方法返回的均是deferred物件。

現在的ajax操作的寫法是:

$.ajax({})
  .done(function(){ alert("success!"); })
  .fail(function(){ alert("fail!"); });

兩種寫法對比可以很明顯的看出來,done()相當於傳統ajax操作的success方法,fail()相當於傳統ajax操作的fail方法。相對於傳統的寫法,程式碼可讀性提高了。

3.deferred物件的方法

3.1基本用法

(1).生成deferred物件

var dfd = $.Deferred(); //create a deferred object

(2).deferred物件的狀態

deferred物件有三種狀態

  • pending:表示操作處於未完成的狀態,任何deferred(延遲)物件開始於pending狀態。
  • resolved:表示操作成功。
  • rejected:表示操作失敗。

state()方法返回deferred物件的當前狀態。

$.Deferred().state(); // 'pending'
$.Deferred().resolve().state(); // 'resolved'
$.Deferred().reject().state(); // 'rejected'

(3).改變deferred物件的狀態

呼叫deferred.resolve() 或者 deferred.resolveWith()轉換Deferred(遞延)到resolved(解決)的狀態,並立即執行設定中任何的doneCallbacks

var callbackFunc = function(){console.log(arguments[0]);}
var dfd = $.Deferred();
dfd.done(callbackFunc);
dfd.resolve("hello");  //'hello'

呼叫deferred.reject() 或者 deferred.rejectWith()轉換Deferred(遞延)到rejected(拒絕)的狀態,並立即執行設定中任何的failCallbacks

var callbackFunc = function(){console.log(arguments[0]);}
var dfd = $.Deferred();
dfd.fail(callbackFunc);
dfd.reject("fail");  //'fail'

(4).繫結回撥函式

deferred物件狀態改變的時候,會觸發回撥函式。任何回撥使用deferred.then()deferred.always()deferred.done()或者 deferred.fail()新增到這個物件都是排隊等待執行。

  • pending-->resolved,執行設定中任何的doneCallbacks(done()指定),引數由resolved傳遞給doneCallbacks。
  • pending-->rejected,執行設定中任何的failCallbacks(fail()指定),引數由reject傳遞給failCallbacks。
  • pending-->resolved/rejected,執行always()指定的callbacks,引數由resolved/rejected傳遞給callbacks。
var f1 = function(){console.log("done");}, 
     f2 = function(){console.log("fail");}, 
     f3 = function(){console.log("always");};

var dfd = $.Deferred();
dfd.done(f1).fail(f2).always(f3);

//if
dfd.resolve(); //'done' 'always'
//if
dfd.reject(); //'fail' 'always'

如果在狀態更改後附加一個callback則會立即執行callback,因此不必擔心deferred物件何時被resolved或者rejected,因為無論何時,引數都會正確地傳遞給callbacks。

var fun1 = function(){console.log(arguments[0]);},
    fun1 = function(){console.log(arguments[0]);};
var dfd = $.Deferred();
dfd.done(fun1);
dfd.resolve("hello"); //'hello'
dfd.done(fun2); //'hello'

3.2.deferred物件的方法

(1)$.Deferred([beforeStart]) -- 建立一個deferred物件,引數型別為Function,是一個在建構函式之前呼叫的函式。

var func = function(){console.log("start");} 
var dfd = $.Deferred(func); //'start'  create a deferred object

(2)deferred.done(doneCallbacks [,doneCallbacks]) -- 當deferred(延遲)物件解決時,呼叫新增處理程式。

args:接受一個或者多個引數,所有的引數都可以是一個單一的函式或者函式陣列,當deferred(延遲)物件解決時,doneCallbacks被呼叫。回撥是依照他們新增的順序執行的。

var func1 = function(){console.log("1");},
     func2 = function(){console.log("2");},
     func3 = function(){console.log("3");};
var dfd = $.Deferred();
dfd.done([func1,func2],func3,[func2,func1]);
dfd.resolve(); // "1 2 3 2 1"

(3)deferred.fail(failCallbacks [,failCallbacks]) -- 當deferred(延遲)物件拒絕時,呼叫新增處理程式。

args:接受一個或者多個引數,所有的引數都可以是一個單一的函式或者函式陣列,當deferred(延遲)物件拒絕時,failCallbacks被呼叫。回撥是依照他們新增的順序執行的。

var func1 = function(){console.log("1");},
     func2 = function(){console.log("2");},
     func3 = function(){console.log("3");};
var dfd = $.Deferred();
dfd.fail([func1,func2],func3,[func2,func1]);
dfd.reject(); // "1 2 3 2 1"

(4)deferred.resolve(args) and deferred.resolveWith(context [,args]) -- 解決Deferred(延遲)物件,並根據給定的args引數(resolveWith給定context)呼叫任何doneCallbacks。

引數:args -- type(object),傳遞給回撥函式(doneCallbacks)的可選的引數,

        context -- type(object),Context(上下文)作為this物件傳遞給完成回撥函式(doneCallbacks)。

var func = function(arg){console.log(arg);};
$.Deferred().done(func).resolve("done!"); //'done!'
var func = function(arg1,arg2){console.log(arg1.name + ',' + arg2);};
$.Deferred().done(func).resolve({name:'Lucy'},'How are you!'); // 'Lucy,How are you!'

resolve和resolveWith的區別就等同於fire和fireWith的區別。

var func = function () {
    console.log(this.name + ',' + arguments[0] + ' ' + arguments[1] + ' ' + arguments[2]);
};
$.Deferred().done(func).resolveWith({ name: "Lucy" }, ["How", "are", "you!"]);//'Lucy,How are you!'

(5)deferred.reject(args) and deferred.rejectWith(context [,args]) -- 拒絕Deferred(延遲)物件,並根據給定的args引數(rejectWith給定context)呼叫任何failCallbacks。

引數:args -- type(object),傳遞給回撥函式(doneCallbacks)的可選的引數,

        context -- type(object),Context(上下文)作為this物件傳遞給完成回撥函式(doneCallbacks)。

var func = function(arg){console.log(arg);};
$.Deferred().fail(func).reject("error!"); //'error!'
var func = function(ctx,arg){console.log(ctx.name + ',' + arg);};
$.Deferred().fail(func).reject({name:'Mark'},'What happend!'); // 'Mark,What happend!'

reject和rejectWith的區別就等同於fire和fireWith的區別。

var func = function () {
    console.log(this.name + ',' + arguments[0] + ' ' + arguments[1]);
};
$.Deferred().fail(func).rejectWith({ name: "Mark" }, ["what", "happend!"]); // 'Mark,What happend!'

(6)deferred.promise([target]) -- 返回Deferred(延遲)的Promise(承諾)物件。

 引數可選,無引數時返回一個Promise(承諾)物件,Promise(承諾)物件僅會暴露那些需要繫結額外的處理或判斷狀態的延遲方法(thendonefailalways,pipeprogressstate,和 promise)時,並不會暴露任何用於改變狀態的延遲方法(resolverejectnotify,resolveWithrejectWith, 和 notifyWith)。使用Promise(承諾)會阻止其他人破壞你製造的promise。

function asyncEvent() {
      var dfd = jQuery.Deferred();

       // Resolve after a random interval
       setTimeout(function () {
             dfd.resolve("hurray");
       }, Math.floor(400 + Math.random() * 2000));

        // Reject after a random interval
        setTimeout(function () {
              dfd.reject("sorry");
        }, Math.floor(400 + Math.random() * 2000));

        // Show a "working..." message every half-second
        setTimeout(function working() {
              if (dfd.state() === "pending") {
                    dfd.notify("working... ");
                     setTimeout(working, 500);
                }
          }, 1);

           // Return the Promise so caller can't change the Deferred
           return dfd.promise();
 }

// Attach a done, fail, and progress handler for the asyncEvent
$.when(asyncEvent()).then(
       function (status) {
             alert(status + ", things are going well");
       },
       function (status) {
              alert(status + ", you fail this time");
       },
       function (status) {
              alert(status);
       }
);

有引數時,會將事件繫結到引數上,然後返回該引數物件(返回的實際是一個擴充套件的Promise(承諾)物件)。

var obj = {
    hello: function (name) {
        alert("Hello " + name);
    }
},
// Create a Deferred
dfd = $.Deferred();

// Set object as a promise
dfd.promise(obj);

// Resolve the deferred
dfd.resolve("John");

// Use the object as a Promise
obj.done(function (name) {
     obj.hello(name); // will alert "Hello John"
}).hello("Karl");

(7)$.when(deferreds) -- 提供一種方法來執行一個或多個物件的回撥函式。

引數:type(Deferred),一個或多個延遲物件,或者普通的JavaScript物件。

  1. 引數僅傳入一個單獨的Deferred物件,返回它的Promise物件。
function func() {
    var dfd = $.Deferred();
    setTimeout(function () {
        dfd.resolve("hurry");
    }, 500);
    return dfd.promise();
};

$.when(func()).done(function (arg) {
    alert(arg); /*alert "hurry"*/
});

     2.引數傳入一個非Deferred和Promise物件,那麼該引數會被當成一個被解決(resolved)的延遲物件,並且繫結到上面的任何doneCallbacks都會被立即執行。

$.when( { name: 123 } ).done(
    function(arg) { alert(arg.name); } /* alerts "123" */
);

     3.無引數,返回一個resolved(解決)狀態的Promise物件。

$.when().state(); // "resolved"

   4.引數為多個Deferred物件,該方法根據一個新的“宿主” Deferred(延遲)物件,跟蹤所有已通過Deferreds聚集狀態,返回一個Promise物件。當所有的延遲物件被解決(resolve)時,“宿主” Deferred(延遲)物件才會解決(resolved)該方法,或者當其中有一個延遲物件被拒絕(rejected)時,“宿主” Deferred(延遲)物件就會reject(拒絕)該方法。

var d1 = $.Deferred();
var d2 = $.Deferred();
 
$.when( d1, d2 ).done(function ( v1, v2 ) {
    console.log( v1 ); // "Fish"
    console.log( v2 ); // "Pizza"
});
 
d1.resolve( "Fish" );
d2.resolve( "Pizza" );

(8)deferred.then(doneFilter [,failFilter] [,progressFilter]) -- 當Deferred(延遲)物件解決,拒絕或仍在進行中時,呼叫新增處理程式。

引數:

  • doneFilter --   type(Function),當Deferred(延遲)物件得到解決時被呼叫的一個函式。
  • failFilter --   type(Function),當Deferred(延遲)物件拒絕時被呼叫的一個函式,可選。
  • progressFilter --   type(Function),當Deferred(延遲)物件生成進度通知時被呼叫的一個函式,可選。

其實,then方法可以理解成,把done(),fail(),progress()合在一起寫。

var filterResolve = function () {
     var dfd = $.Deferred(),
          filtered = dfd.then(function (value) { return value * 2; });
      dfd.resolve(5);
      filtered.done(function (value) { console.log(value); });
};
filterResolve(); //'10'

var defer = $.Deferred(),
     filtered = defer.then(null, function (value) {
         return value * 3;
      });

defer.reject(6);
filtered.fail(function (value) {
      alert("Value is 3*6 = " + value);
});

(9)deferred.always(alwaysCallbacks [,alwaysCallbacks]) -- 當Deferred(延遲)物件解決或拒絕時,執行alwaysCallbacks。

 顧名思義,只要Deferred物件的狀態發生更改(解決或者拒絕)均會呼叫alwaysCallbacks。

(10)deferred.state() -- 獲取一個Deferred(延遲)物件的當前狀態,不接受任何引數。

$.Deferred().state();//"pending"

上面講述過deferre(延遲)物件的三種狀態,這個方法對於debug非常有用,例如,在準備reject一個deferred物件之前,判斷它是否處於resolved狀態。

(11)deferred.notify(args) and deferred.notifyWith(context,args)

引數我就不多解釋了,和上面resolve()和reject()的引數都是一樣,只不過這些引數是progressCallbacks,即進行中的回撥。

function doSomething() {   
     var dfd = $.Deferred();

     var count = 0;
     var intervalId = setInterval(function() {
          dfd.notify(count++);
          count > 3 && clearInterval(intervalId);
      }, 500);

      return dfd.promise();
};

var promise = doSomething();

promise.progress(function (prog) {
      console.log(prog); // '0', '1', '2', '3' 
});

notifyWith()和notify()的區別就是fire()和fireWith()的區別,用法請參開resolve()和resolveWith()的例子。

(12)deferred.progress(progressCallbacks, progressCallbacks) -- 當Deferred(延遲)物件生成正在執行中的進度通知時,呼叫progressCallbacks。

前一個引數是函式或陣列,後一個引數也是函式或陣列,但是是可選的。

這個方法的解釋非常的抽象,剛開始的時候不知道什麼叫生成正在執行中的進度通知,其實這個progress()是需要和notify()或者notifyWith()方法結合一起使用的,程式碼請參考notify()的示例。

(13).promise([type] [, target]) -- 返回一個Promise(承諾)物件,用來觀察當某種型別的所有行動繫結到集合,排隊與否還是已經完成。此方法主要用於animations和ajax操作。

引數:type(預設:fx)(型別:String)需要待觀察佇列型別,這個概念很模糊,拿預設值來說,"fx"意味著返回被resolve的Promise物件的時機,是在所有被選中元素的動畫都完成時發生的。

        target(型別:PlainObject)將要繫結promise方法的物件,也就是如果提供target引數,.promise()在該target引數上新增方法,然後返回這個物件,而不是建立一個新的,這種方式適用於在一個已經存在的物件上新增                      Promise行為的情況。不提供該引數的話,.promise()會返回一個新建立的Promise物件。

1.在沒有啟用動畫的集合上呼叫.promise(),返回一個resolved的Promise物件,此時該Promise物件上的doneCallbacks會立即執行。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="jquery-1.9.0.js"></script>
    <script type="text/javascript">
        $(function () {
            var div = $("<div />");
            var promise = div.promise();
            promise.done(function (arg1) {
                // will fire right away and alert "true"
                alert(this === div && arg1 === div);
            });
        });
    </script>
</head>
<body>
    <div></div>
</body>
</html>

2.當所有的動畫結果時(包括哪些在動畫回撥函式和之後新增的回撥函式中初始化的動畫),resolve返回的Promise。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="jquery-1.9.0.js"></script>
    <style>
        div {
            height: 50px;
            width: 50px;
            float: left;
            margin-right: 10px;
            display: none;
            background-color: #090;
        }
    </style>
    <script type="text/javascript">
        $(function () {
            $("button").bind("click", function () {
                $("p").append("Started...");

                $("div").each(function (i) {
                    $(this).fadeIn().fadeOut(1000 * (i + 1));
                });

                $("div").promise().done(function () {
                    $("p").append(" Finished! ");
                });
            });
        });
    </script>
</head>
<body>
    <button>Go</button>
    <p>Ready...</p>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
</body>
</html>

(14)deferred.isRejected() 和 deferred.isResolved() --  從jQuery 1.7開始被棄用,較新版本的jQuery類庫中已經被刪除,可以使用state()方法代替這兩個方法。

(15)deferred.pipe() -- 從jQuery 1.8開始被棄用,可使用deferred.then()替代。

4.什麼情況下使用deferred物件和Promises?

上面講了很多,那麼我們究竟在什麼情況下使用Deferred物件和Promises物件呢?

(1)複雜的動畫

不知道動畫什麼時候結束,但是又必須在動畫結束的時候做一些操作或者是啟動其他的動畫,這種情況下,如果採用其他的方式,很容易導致程式碼可讀性差,尤其是還夾帶著一些其它的操作,比如渲染、表單操作等,現在jQuery會為你的動畫操作返回一個Promise,這樣這些動畫可以進行鏈式操作。

(2)處理佇列

window.queue = $.when() $('#list').on('click', function() { window.queue = window.queue.then(function() { //do the thing }) } )

(3)The Wait promise

function wait(ms) { 
    var deferred = $.Deferred(); 
    setTimeout(function(){deferred.resolve()}, ms);
    return deferred.promise(); 
} 

wait(1500).then(function () {
       // After 1500ms this will be executed 
});

(4)典型的Ajax操作

$.when($.ajax({}), $.ajax({}))
  .done(function(){ alert("success!"); })
  .fail(function(){ alert("error!"); });

(5)一些耗時的大迴圈操作

5.參考連結

[1] Graham Jenson, JQuery Promises and Deferreds: I promise this will be short

[2] 阮一峰, jQuery的deferred物件詳解

[3]jQuery API, Deferred Object

[4]José F. Romaniello, Understanding JQuery.Deferred and Promise

[5]Matt Baker, jQuery.Deferred is the most important client-side tool you have

[6]Stackoverflow JQuery Deferred. Using $.when and .progress()

[7]http://javascript.ruanyifeng.com/jquery/deferred.html

$.Deferred().state();//"pending"