2015年12月13日 星期日

jQuery之Deffered的應用

jQuery有Deferred及Promise物件,可以幫助我們處理異步函式的callback順序關係。
在這邊舉兩個例子來說明其可能的兩個應用:

一、用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的原理,在這邊稍微講一下大概的背後過程:

  1. f1()回傳了第一個Deferred物件。
  2. f1('f1').then(f2)建立了第二個Deferred物件,並為第一個Deferred物件設定done(ff2),ff2是callback函式,ff2的內容是執行f2(),並取得f2()回傳的第三個Deferred物件,接著設定第三個物件的done(fff2),其中fff2=第二個Deferred物件.resolve。
  3. 所以當呼叫then(f2)時,會回傳一個Deferred物件(即第二個Deferred物件。
  4. 當f1()裡呼叫defer.resolve()時,會執行f2()。
  5. 當f2()裡呼叫defer.resolve()時,會引發第三個Deferred物件的done(),接著就會引發第二個Deferred物件的resolve()。
  6. 以此類推,第二個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.

  1. 因為$.when()不能接受Array型式的參數,所以這裡利用了apply($,des)來將des中的內容傳給when(),其中第一個參數為要代替when()中的this的參考,第二個參數是一個Array,其內容為要傳給when()的參數。
  2. 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();
  }
}

沒有留言 :

張貼留言