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對象究竟是個什麼玩意呢?
deferred對象即延遲對象,它是jQuery 1.5版本引入的一種回調函數的解決方案,代表了將要完成的某種操作,並且提供了一些方法,幫助用戶使用。
deferred對象是對Promises接口的實現。jQuery 1.5版本以及之後所有的Ajax返回的jqXHR對象就是一個deferred對象。
deferred對象的好處之一,就是它允許你為一個操作添加多個回調函數,這在傳統的ajax中是無法實現的。
$.ajax("test.html")
.done(function(){ alert("first success callback!");} )
.fail(function(){ alert("there is an error!"); } )
.done(function(){ alert("second success callback!");} );
deferred對象的好處之二,就是它允許你為多個操作指定同一個回調函數,這在傳統的ajax中也是無法實現的。
$.when($.ajax({}), $.ajax({}))
.done(function(){ alert("success!"); })
.fail(function(){ alert("error!"); });
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!"); });
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方法。相對於傳統的寫法,代碼可讀性提高了。
(1).生成deferred對象
var dfd = $.Deferred(); //create a deferred object
(2).deferred對象的狀態
deferred對象有三種狀態
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()添加到這個對象都是排隊等待執行。
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'
(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(承諾)對象僅會暴露那些需要綁定額外的處理或判斷狀態的延遲方法(then, done, fail, always,pipe, progress, state,和 promise)時,並不會暴露任何用於改變狀態的延遲方法(resolve, reject, notify,resolveWith, rejectWith, 和 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對象。
function func() {
var dfd = $.Deferred();
setTimeout(function () {
dfd.resolve("hurry");
}, 500);
return dfd.promise();
};
$.when(func()).done(function (arg) {
alert(arg); /*alert "hurry"*/
});
$.when( { name: 123 } ).done(
function(arg) { alert(arg.name); } /* alerts "123" */
);
$.when().state(); // "resolved"
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(延遲)對象解決,拒絕或仍在進行中時,調用添加處理程序。
參數:
其實,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>
View Code
(14)deferred.isRejected() 和 deferred.isResolved() -- 從jQuery 1.7開始被棄用,較新版本的jQuery類庫中已經被刪除,可以使用state()方法代替這兩個方法。
(15)deferred.pipe() -- 從jQuery 1.8開始被棄用,可使用deferred.then()替代。
上面講了很多,那麼我們究竟在什麼情況下使用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)一些耗時的大循環操作
[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()
未完待續。。。