jQuery 的deferred物件

luckyyulin發表於2017-06-28

jQuery 的deferred物件

參考阮一峰部落格【jQuery的deferred物件詳解】篇

優勢

優勢一: 指定同一操作的多個回撥函式

回撥函式可以新增任意多個,它們按照新增順序執行。

$.ajax("test.html")
 .done(function(){ alert("哈哈,成功了!");} )
 .fail(function(){ alert("出錯啦!"); } )
 .done(function(){ alert("第二個回撥函式!");} );

優勢二: 為多個操作指定回撥函式

// 先執行兩個操作$.ajax("test1.html")和$.ajax("test2.html"),如果都成功了,就執行done()指定的回撥函式;如果有一個失敗或都失敗了,就執行fail()指定的回撥函式。
$.when($.ajax("test1.html"), $.ajax("test2.html"))
 .done(function(){ alert("哈哈,成功了!"); })
 .fail(function(){ alert("出錯啦!"); });

優勢三: 把這一套回撥函式介面,從ajax操作擴充套件到了所有操作。不管是ajax操作還是本地操作,也不管是非同步操作還是同步操作都可以使用deferred物件的各種方法,指定回撥函式。

demo1: 耗時wait

var wait = function(){
  var tasks = function(){
    alert("執行完畢!");
  };
  setTimeout(tasks,5000);
};

demo2: 將wait改成回撥函式

$.when(wait())
 .done(function(){ alert("哈哈,成功了!"); })
 .fail(function(){ alert("出錯啦!"); });

但是,這樣寫的話,done()方法會立即執行,起不到回撥函式的作用。原因在於$.when()的引數只能是deferred物件,所以必須對wait()進行改寫

demo3: 把wait改寫成deferred物件

var dtd = $.Deferred(); // 新建一個deferred物件
var wait = function(dtd){
  var tasks = function(){
    alert("執行完畢!");
    dtd.resolve(); // 改變deferred物件的執行狀態
  };
  setTimeout(tasks,5000);
  return dtd;
};

$.when(wait(dtd))
 .done(function(){ alert("哈哈,成功了!"); })
 .fail(function(){ alert("出錯啦!"); });

現在,wait()函式返回的是deferred物件,這就可以加上鍊式操作了。可是,dtd是一個全域性物件,所以它的執行狀態可以從外部改變。

var dtd = $.Deferred(); // 新建一個Deferred物件
var wait = function(dtd){
  var tasks = function(){
    alert("執行完畢!");
    dtd.resolve(); // 改變Deferred物件的執行狀態
  };
  setTimeout(tasks,5000);
  return dtd;
};
$.when(wait(dtd))
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出錯啦!"); });
dtd.resolve();

在程式碼的尾部加了一行dtd.resolve(),這就改變了dtd物件的執行狀態,因此導致done()方法立刻執行,跳出”哈哈,成功了!”的提示框,等5秒之後再跳出”執行完畢!”的提示框。

demo4: deferred.promise()方法

為了避免這種情況,jQuery提供了deferred.promise()方法。它的作用是,在原來的deferred物件上返回另一個deferred物件,後者只開放與改變執行狀態無關的方法(比如done()方法和fail()方法),遮蔽與改變執行狀態有關的方法(比如resolve()方法和reject()方法),從而使得執行狀態不能被改變。

var dtd = $.Deferred(); // 新建一個Deferred物件
var wait = function(dtd){
  var tasks = function(){
    alert("執行完畢!");
    dtd.resolve(); // 改變Deferred物件的執行狀態
  };
  setTimeout(tasks,5000);
  return dtd.promise(); // 返回promise物件
};
var d = wait(dtd); // 新建一個d物件,改為對這個物件進行操作
$.when(d)
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出錯啦!"); });
d.resolve(); // 此時,這個語句是無效的

在上面的這段程式碼中,wait()函式返回的是promise物件。然後,我們把回撥函式繫結在這個物件上面,而不是原來的deferred物件上面。這樣的好處是,無法改變這個物件的執行狀態,要想改變執行狀態,只能操作原來的deferred物件。

不過,更好的寫法是allenm所指出的,將dtd物件變成wait()函式的內部物件。

var wait = function(){
  var dtd = $.Deferred(); //在函式內部,新建一個Deferred物件
  var tasks = function(){
    alert("執行完畢!");
    dtd.resolve(); // 改變Deferred物件的執行狀態
  };
  setTimeout(tasks,5000);
  return dtd.promise(); // 返回promise物件
};
$.when(wait())
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出錯啦!"); });

另一種防止執行狀態被外部改變的方法,是使用deferred物件的建構函式$.Deferred()。
這時,wait函式還是保持不變,我們直接把它傳入$.Deferred()

$.Deferred(wait)
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出錯啦!"); });

jQuery規定,$.Deferred()可以接受一個函式名(注意,是函式名)作為引數,$.Deferred()所生成的deferred物件將作為這個函式的預設引數。

除了上面兩種方法以外,我們還可以直接在wait物件上部署deferred介面。

var dtd = $.Deferred(); // 生成Deferred物件
var wait = function(dtd){
  var tasks = function(){
    alert("執行完畢!");
    dtd.resolve(); // 改變Deferred物件的執行狀態
  };
  setTimeout(tasks,5000);
};
dtd.promise(wait);
wait.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出錯啦!"); });
wait(dtd);

這裡的關鍵是dtd.promise(wait)這一行,它的作用就是在wait物件上部署Deferred介面。正是因為有了這一行,後面才能直接在wait上面呼叫done()和fail()。

狀態

三種執行狀態: 未完成、完成和已失敗

  • 未完成: 繼續等待,或者呼叫progress()方法指定的回撥函式

  • 已完成: 呼叫done()方法指定的回撥函式

  • 已失敗: 呼叫fail()方法指定的回撥函式

改變狀態函式

在ajax操作時,deferred物件會根據返回結果自動改變自身的執行狀態;但是在非非同步中執行狀態必須由程式設計師手動指定。

  • dtd.resolve(): 將dtd物件的執行狀態從”未完成”改為”已完成”,從而觸發done()方法。

  • dtd.reject(): 將dtd物件的執行狀態從”未完成”改為”已失敗”,從而觸發fail()方法。

相關文章