jQuery 2.0.3 源碼分析 Deferred概念

 

      JavaScript編程幾乎老是伴隨着異步操做,傳統的異步操做會在操做完成以後,使用回調函數傳回結果,而回調函數中則包含了後續的工做。這也是形成異步編程困難的主要緣由:咱們一直習慣於「線性」地編寫代碼邏輯,可是大量異步操做所帶來的回調函數,會把咱們的算法分解地支離破碎。此時咱們不能用if來實現邏輯分支,也不能用while/for/do來實現循環,更不用提異步操做之間的組合、錯誤處理以及取消操做了。所以也誕生了如jQuery Deferred這樣的輔助類庫css

 

舉一個例子會有助於理解html

我作的是phonegap項目,因此涉及到的異步處理就別特多:java

1. 常見的setTimeoutjquery

2. 與底層代碼通訊,調用插件cordova.exec算法

3. postmessage與iframe通訊數據庫

4. CSS3 動畫編程

5. Ajaxapi

6. HTML5的本地數據promise

等等…瀏覽器

 

我就拿HTML5 Web Database爲例:

數據庫

image

查詢語句

//數據庫連接對象
        config.db = window.openDatabase(config.dbName, "1.0", "Xxtebook Database", config.dbSize);

        config.db.transaction(function(tx) {
                tx.executeSql(
'SELECT * FROM Novel'
, [], function(tx, results) {
                    result = results.rows;
                    console.log('result',result) //
                });
        }, function(){}, function(){});

 

就這段代碼其實自己是沒問題的,可是這裏至少要涉及到異步方面的考慮:

javaScript要求在與服務器進行交互時要用異步通訊,如同AJAX同樣,由於是異步模型,因此在調用transaction遊覽器提供的本地數據接口時候相似AJAX(這裏我是假設),瀏覽器本身有內部的XHR方法異步處理,可是此時的JS代碼仍是會同步往下執行,其實就是無阻塞的代碼

   問題:由於無無阻塞,代碼在發送AJAX這個請求後會繼續執行,那麼後續的操做若是依賴這個數據就會出錯了,因此這裏就須要等待AJAX返回,才能執行後續操做

咱們換個更簡單 僞代碼

alert(1)
setTimeout(function(){
    alert(2)
},0)
alert(3)
//alert(1) 
//alert(3)
//alert(2)

代碼都能看懂,由於異步而致使流程不正確

或者說咱們的應用在某個程度上依賴第三方api的數據,那麼就會面臨一個共同的問題:

咱們沒法獲悉一個API響應的延遲時間,應用程序的其餘部分可能會被阻塞,直到它返回 結果。Deferreds 對這個問題提供了一個更好的解決方案,它是非阻塞的,而且與代碼徹底解耦

固然異步操做也能夠提供一個相似成功回調,失敗回調的通知接口

JS是單線程語言,就簡單性而言,把每一件事情(包括GUI事件和渲染)都放在一個線程裏來處理是一個很好的程序模型,由於這樣就無需再考慮線程同步這些複雜問題。
另外一方面,他也暴露了應用開發中的一個嚴重問題,單線程環境看起來對用戶請求響應迅速,可是當線程忙於處理其它事情時,就不能對用戶的鼠標點擊和鍵盤操做作出響應。

 


What's The Point Of Promises?

http://www.kendoui.com/blogs/teamblog/posts/13-03-28/what-is-the-point-of-promises.aspx

http://wiki.commonjs.org/wiki/Promises/A

大多狀況下,promise做爲一個模型,提供了一個在軟件工程中描述延時(或未來)概念的解決方案。

它背後的思想咱們已經介紹過:不是執行一個方法而後阻塞應用程序等待結果返回,而是返回一個promise對象來知足將來值。

 

這樣看來,Promise/A只是一種規範,Deferred能夠看做這種規範的具體實現

畫了一個很簡短的圖,你們略過一下,後面有很詳細的流程圖

演示文稿11

 

咱們先看看規範如何定義的:

Promise/A提議’定義了一個’then‘方法來註冊回調,當處理函數返回結果時回調會執行。它返回一個promise的僞代碼看起來是這樣的:

promise = function create(
    //數據庫連接對象
    config.db = window.openDatabase(config.dbName, "1.0", "Xxtebook Database", config.dbSize);

    config.db.transaction(function(tx) {
            tx.executeSql('SELECT * FROM Novel', [], function(tx, results) {
                result = results.rows;
                console.log('result',result) //
            });
    }, function(){}, function(){});
);

promise.then(function( futureValue ) {

});
promise.then(function( futureValue ) {
    
});

promise回調會在處於如下兩種不一樣的狀態下執行:

  • resolved:在這種狀況下,數據是可用
  • rejected:在這種狀況下,出現了錯誤,沒有可用的值

'then'方法接受兩個參數:一個用於promise獲得瞭解決(resolved),另外一個用於promise拒絕(rejected)。讓咱們回到僞代碼

promise.then( function( futureValue ) {   
   //成功    
} , function() {   
  //失敗
} );

在某些狀況下,咱們須要得到多個返回結果後,再繼續執行應用程序(例如,在用戶能夠選擇他們感興趣的選項前,顯示一組動態的選項)。這種狀況下,'when'方法能夠用來解決全部的promise都知足後才能繼續執行的場景。

when(   
  promise1,   
  promise2,  
   ...  
 ).then(function( futureValue1, futureValue2, ... ) {   
     //當全部的異步對象都準備完畢後,才執行
 });

咱們在模擬一個場景

若是同時執行多個動畫的時候,咱們就須要對每個動畫執行完畢後的處理下一個動做,意味着咱們就要監聽全部動畫的執行完畢後的回調

使用promise和‘when’方式卻能夠很直截了當的表示: 一旦動畫執行完成,就能夠執行下一步任務。最終的結果是咱們能夠能夠簡單的用一個回調來解決多個動畫執行結果的等待問題

when( function(){   
   //執行動畫1
   return promise 
}, function(){  
  //執行動畫2
  return promise 2 
} ).then(function(){  
   //當2個動畫完成後,咱們在自行後續的代碼   
});

基本上能夠用非阻塞的邏輯方式編寫代碼並異步執行。 而不是直接將回調傳遞給函數,這可能會致使緊耦合的接口,經過promise模式能夠很容易區分同步和異步的概念。

根據這個規範,咱們看看JQ是如何實現的?

 


Deferred Object :http://api.jquery.com/category/deferred-object/

因爲1.7版本後回調函數Callbacks從Deferred中抽離出去了,因此在此以前還須要瞭解下 回調函數-callback

本文主要是涉及源碼的實現

咱們先對API有個總體的認識

jQuery.Deferred()

一個構造函數,返回一個鏈式實用對象方法來註冊多個回調,回調隊列, 調用回調隊列,並轉達任何同步或異步函數的成功或失敗狀態。

deferred.always()

當Deferred(延遲)對象解決或拒絕時,調用添加處理程序

deferred.done()

當Deferred(延遲)對象解決時,調用添加處理程序

deferred.fail()

當Deferred(延遲)對象拒絕時,調用添加處理程序。

deferred.isRejected()

肯定一個Deferred(延遲)對象是否已被拒絕。

deferred.isResolved()

肯定一個Deferred(延遲)對象是否已被解決。

deferred.notify()

根據給定的 args參數 調用Deferred(延遲)對象上進行中的回調 (progressCallbacks)

deferred.notifyWith()

根據給定的上下文(context)和args遞延調用Deferred(延遲)對象上進行中的回調(progressCallbacks )

deferred.pipe()

實用的方法來過濾 and/or 鏈Deferreds。

deferred.progress()

當Deferred(延遲)對象生成進度通知時,調用添加處理程

deferred.promise()

返回Deferred(延遲)的Promise(承諾)對象。

deferred.reject()

拒絕Deferred(延遲)對象,並根據給定的args參數調用任何失敗回調函數(failCallbacks)

deferred.rejectWith()

拒絕Deferred(延遲)對象,並根據給定的 context和args參數調用任何失敗回調函數(failCallbacks)。

deferred.resolve()

解決Deferred(延遲)對象,並根據給定的args參數調用任何完成回調函數(doneCallbacks)

deferred.resolveWith()

解決Deferred(延遲)對象,並根據給定的 context和args參數調用任何完成回調函數(doneCallbacks)

deferred.state()

肯定一個Deferred(延遲)對象的當前狀態

deferred.then()

當Deferred(延遲)對象解決,拒絕或仍在進行中時,調用添加處理程序。

jQuery.when()

提供一種方法來執行一個或多個對象的回調函數, Deferred(延遲)對象一般表示異步事件。

.promise()

返回一個 Promise 對象用來觀察當某種類型的全部行動綁定到集合,排隊與否仍是已經完成

 

構建一個異步對象:

咱們傳統的寫法

function aaa(callback){
  setTimeout(function(){
    callback( 5 );    
  },1000);
}

function done(value){
    alert(value)
}

aaa(function(value){
   done(value);
})

換成JQuery的寫法

 

var defer = $.Deferred(); //構建異步對象

  setTimeout(function(){
    defer.resolve( 5 );    
  },1000);

  var filtered  = defer.then(function( value ) {
        return value * 2;
  });

  filtered.done(function( value ) {
      console.log('打印出值',value)
  });

經過對比咱們仍是能感受到很大的不一樣:

傳統的代碼邏輯,經過嵌套回調函數,等待異步發送成功的通知後,在執行下一步操做,若是有大量的嵌套回調,耦合度,維護性,擴展性?

那麼Deferred對象存在的意義在哪裏?

從我角度來講,對於開發者

1:代碼可讀性,扁平化結構

2:讓支離破碎的代碼結構,繼續保存線性的代碼邏輯,也就是異步代碼轉爲同步

3: 從抽線的角度,提供了一個抽象的非阻塞的解決方案(如 Ajax 請求的響應),它建立一個 「promise」 對象,其目的是在將來某個時間點返回一個響應。

deferreds 能夠理解爲表示須要長時間才能完成的耗時操做的一種方式,相比於阻塞式函數它們是異步的,而不是阻塞應用程序等待其完成而後返回結果。 deferred對 象會當即返回,而後你能夠把回調函數綁定到deferred對象上,它們會在異步處理完成後被調用。

因此,總的來說Deferred經過一組 API 來規範化異步操做,這樣也可以讓異步操做的流程控制更加容易

 

下章就是具體的源碼分析了,會有附帶流程圖及源碼的分析

 

相似的異步庫能夠參考

http://www.cnblogs.com/aaronjs/p/3169328.html

http://www.cnblogs.com/aaronjs/p/3168588.html

基本都大同小異

相關文章
相關標籤/搜索