最近在寫專案時,遇到使用$.when()方法處理ajax非同步問題的情境,這邊把deferred物件整理起來與大家分享。
假如有一個判斷式或後續的程式需要ajax返回的物件:
錯誤方式
var id1, id2;
$.get("main/func1/").done(function(data1) {
id1 = data1;
});
$.post("main/func2/","condition").done(function(data2) {
id2 = data2;
});
alert('id1=' + id1 + ',' + 'id2=' + id2);
執行結果id1跟id2都是undefined。由於ajax是非同步的,ajax還沒返回物件,下面的程式就已經跑完了
使用巢狀ajax的方式
var id1, id2;
$.get("main/func1/").done(function(data1) {
id1 = data1;
$.post("main/func2/","condition").done(function(data2) {
id2 = data2;
alert('id1=' + id1 + ',' + 'id2=' + id2);
});
});
執行結果id1跟id2雖能取得指定的資料,但若需要2個以上的Ajax,又會繼續巢狀,如此程式也會變得難以維護
使用$.when()
var ajax1 = $.get("main/func1/");
var ajax2 = $.post("main/func2/","condition");
var ajax3 = $.get("main/func3/");
$.when(ajax1,ajax2,ajax3).done(function(id1,id2,id3){
alert('id1=' + id1 + ',' + 'id2=' + id2 + 'id3=' + id3);
}).fail(function(){ alert('error'); });
$.when()可使用多個deferred物件作為引數,當它們全部執行成功後,才done回撥函式,但只要其中有一個失敗,就呼叫fail回撥函式,關於$.when()下面的介紹會再詳細說明。關於ajax非同步問題可以善用deferred物件處理。
Deferred物件
其實平時寫jQuery時使用Ajax的時候,常看到的done()、fail()就是deferred物件。
deferred物件是jQuery對Promises介面的實現。它是非同步操作的通用介面,可以被看作是一個等待完成的任務,開發者通過一些通過的介面對其進行設定。事實上,它扮演代理人(proxy)的角色,將那些非同步操作包裝成具有某些統一特性的物件,典型例子就是ajax操作、網頁動畫、web worker等等。 jQuery的所有Ajax操作函式,預設返回的就是一個deferred物件。
那什麼是Promises?
由於JavaScript單執行緒的特點,如果某個操作耗時很長,其他操作就必需排隊等待。為了避免整個程式失去響應,通常的解決方法是將那些排在後面的操作,寫成回撥函式(callback)的形式。這樣做雖然可以解決問題,但是有一些顯著缺點:
Promises就是為了解決這些問題而提出的,它的主要目的就是取代callback,成為非同步操作的解決方案。它的核心思想就是讓非同步操作返回一個物件,其他操作都針對這個物件來完成。比如,假定ajax操作返回一個Promise物件。
var promise = $.get('http://www.testgss.com')
Promise物件有一個then方法,可以用來指定callback。一旦非同步操作完成,就呼叫指定的函式。
$.get('http://www.testgss.com').then(function (content) {
console.log(content)
})
Deferred物件的方法
Deferred的方法其實蠻多種的,如果想要深入了解的話可以參考以下連結。
https://api.jquery.com/category/deferred-object/
這邊舉幾個常用且實用的方法:
$.deferred()可以生成一個deferred物件。
var deferred = $.deferred();
done() 和 fail()
done()指定非同步操作成功後的callback函式;fail()指定失敗後的callback函式。
var deferred = $.Deferred();
deferred
.done(function(value) {
alert(value);
})
.fail(function(value) {
alert(value);
});
它們返回的是原有的deferred物件,因此可以採用鏈式寫法,在後面再連結別的方法(包括done和fail在內)。
resolve() 和 reject()
這兩個方法用來改變deferred物件的狀態。resolve()將狀態改為非同步操作成功,reject()改為操作失敗。
var deferred = $.Deferred();
deferred
.done(function(value) {
alert(value + '-done');
})
.fail(function() {
alert(value + '-fail');
})
.then(function() {
alert(value + '-then')
});
deferred.resolve("resolve"); // 先跳出"resolve-done"再跳出"resolve-then"
一旦呼叫resolve(),就會依次執行done()和then()方法指定的回撥函式;一旦呼叫reject(),就會依次執行fail()和then()方法指定的callback函式。
always()也是指定callback函式,不管是resolve(執行done)或reject(執行fail)都要呼叫。
$.when()方法
$.when()接受多個deferred物件作為引數,當它們全部執行成功後,才呼叫resolved狀態的回撥函式,但只要其中有一個失敗,就呼叫rejected狀態的callback函式。它相當於將多個非同步操作,合併成一個。
$.when(
$.ajax( "/main.php" ),
$.ajax( "/modules.php" ),
$.ajax( "/lists.php" )
).then(function (result1, result2, result3){
console.log(result1);
console.log(result2);
console.log(result3);
});
上面程式碼要等到三個ajax操作都結束以後,才執行then方法指定的回撥函式。回撥函式有三個引數,result1、result2和result3,依次對應前面三個ajax操作的返回結果。
$.when方法的另一個作用是,如果它的引數返回的不是一個Deferred或Promise物件,那麼when方法的回撥函式將立即執行。