在這邊舉兩個例子來說明其可能的兩個應用:
一、用Deferred實現異步函式的callback順序關係
Deferred可以實現異步函式的callback順序關係,在下例中,我們有三個函式,f1()、f2()、f3(),其中都有setTimeout模擬一秒的異步延遲,並且希望能f1做完才執行f2、f2做完才執行f3。如果我們沒有處理好callback的順序關係的話,就會發生f1的setTimeout還沒執行完就執行f2的情況。
在此例中,我們利用Deferred.then()來確保f1,f2,f3的執行順序關係,在f1,f2,f3中,會個自建立一個Deferred物件並傳回。
而setTimeout結束後,會呼叫Deferred.resolve()來告知函式執行完成,並可以選擇送進一個參數(param)來給之後的callback函式使用,接著會執行then()裡面的函式。
以上說明是簡單的行為了解。其實真正的實作比較複雜,可以參考jQuery之Deferred的原理,在這邊稍微講一下大概的背後過程:
- f1()回傳了第一個Deferred物件。
- f1('f1').then(f2)建立了第二個Deferred物件,並為第一個Deferred物件設定done(ff2),ff2是callback函式,ff2的內容是執行f2(),並取得f2()回傳的第三個Deferred物件,接著設定第三個物件的done(fff2),其中fff2=第二個Deferred物件.resolve。
- 所以當呼叫then(f2)時,會回傳一個Deferred物件(即第二個Deferred物件。
- 當f1()裡呼叫defer.resolve()時,會執行f2()。
- 當f2()裡呼叫defer.resolve()時,會引發第三個Deferred物件的done(),接著就會引發第二個Deferred物件的resolve()。
- 以此類推,第二個Deferred物件被resolve()後就會去執行f3()。
HTML:
<div id='result'> Result: </div>
Javascript:
function f1(param) { var defer = $.Deferred(); setTimeout(function() { $('#result').html($('#result').html() + '<br/>f1 got parameter : ' + param); //將'f2'當參數傳給don()或then()之後的function defer.resolve('f2'); }, 1000); return defer; } function f2(param) { var defer = $.Deferred(); setTimeout(function() { $('#result').html($('#result').html() + '<br/>f2 got parameter : ' + param); //將'f3'當參數傳給don()或then()之後的function defer.resolve('f3'); }, 1000); return defer; } function f3(param) { var defer = $.Deferred(); setTimeout(function() { $('#result').html($('#result').html() + '<br/>f3 got parameter : ' + param); //defer.resolve(param + ' f3'); }, 1000); return defer; } //Deferred參數的傳遞 f1('f1').then(f2).then(f3);二、合併多個異步函式的callback處理,即個別異步函式都執行完後才執行callback
在此例中我們用到了$.when(),它可以被傳入多個Deferred或Promise物件,並在所有的Deferred或Promise物件都被resolve()或reject()後才執行callback。
在這裡我們有三個動畫,都為一串文字由右往左移動,每個動畫的時間都不一樣,第一個是用CSS的動畫,其他兩個是用jQuery的animate()做的動畫。
對於每個動畫,我們都建立一個新的Deferred物件給它,並在動畫執行完後將它得到的Deferred給resolve()。而每個Deferred物件我們都放進一個叫des的Array中。
最後我們把Array裡面的三個Deferred物件丟給$.when()裡當參數,利用then()或done()來設定三個Deferred物件都resolve()後才要執行的callback。
P.S.
- 因為$.when()不能接受Array型式的參數,所以這裡利用了apply($,des)來將des中的內容傳給when(),其中第一個參數為要代替when()中的this的參考,第二個參數是一個Array,其內容為要傳給when()的參數。
- resolveDeferred()接受一個Deferred物件,並傳回一個function,在傳回的function中其this指向呼叫resolveDeferred的物件;而deferred指向function被建立的域(即resolveDeferred())中的deferred。(可參見閉包(Closure))
HTML:
<div class='animSection'> <div id='anim1'>Watch me move1</div> <div id='anim2'>Watch me move2</div> <div id='anim3'>Watch me move3</div> </div> <br/> <div id='text' class="text">Detect Result:</div>
CSS:
.animSection div{ padding-left: 100%; } .animClass { animation-name: myAnim; animation-duration: 6s; animation-fill-mode: forwards; } @keyframes myAnim { 0% { padding-left: 100%; } 100% { padding-left: 0%; } }
Javascript:
var des = []; //用來放Deferred或Promise物件的array var deferred; deferred = $.Deferred(); des.push(deferred.promise()); //也可以寫成des.push(deferred),deferred.promise()回傳的為Promise物件,為不可修改狀態只供查詢狀態的物件,即沒有resolve()等方法 // $('#anim1').addClass('animClass').one('animationend', resolveDeferred(deferred)); deferred = $.Deferred(); des.push(deferred.promise()); $('#anim2').animate({ paddingLeft: "-=100%" }, 9000, resolveDeferred(deferred)); deferred = $.Deferred(); des.push(deferred.promise()); $('#anim3').animate({ paddingLeft: "-=100%" }, 5000, resolveDeferred(deferred)); //因為$.when()只能接收一個一個用逗號分開的參數,所以利用apply來將array以個別 //參數的方式傳給when(),when()可接受Deferred及Promise物件 $.when.apply($, des).done(function() { $('#text').html($('#text').html() + '<br/>All finished!'); }); //也可以寫成以下,then可以給兩個參數,第一個為done()的callback函式, //第二個為fail()的callback函式 //$.when.apply($, des).then(function(){ // $('#text').html($('#text').html() + '<br/>All finished!'); //}); function resolveDeferred(deferred) { return function() { $('#text').html($('#text').html() + '<br/>#' + this.id + ' finished!'); deferred.resolve(); } }