jQuery源碼分析系列(31) : Ajax deferred實現


AJAX的底層實現都是瀏覽器提供的,所以任何基於api上面的框架或者庫,都只是說對於功能的靈活與兼容維護性做出最優的擴展

ajax請求的流程:

1、通過 new XMLHttpRequest 或其它的形式(指IE)生成ajax的對象xhr。  

2、通過xhr.open(type, url, async, username, password)的形式建立一個連接。 

3、通過setRequestHeader設定xhr的請求頭部(request header)。  

4、通過send(data)請求服務器端的數據。

5、執行在xhr上注冊的onreadystatechange回調處理返回數據。

這幾步之中,

我們開發者可能會遇到的問題

1 跨域

2 json的格式

3 dataType

4 AJAX亂碼問題

5 頁面緩存

6 狀態的跟蹤

7 不同平台兼容

jQuery主要就是解決上面這問題,之后就在這個基礎之上進行擴展

 


jQuery2.0.3版的Ajax部分源碼大概有1200多行,主要針對ajax的操作進行了一些擴展,使之更加靈活

jQuery在1.5中對AJAX模塊的重寫,增加了幾個新的概念

AJAX模塊提供了三個新的方法用於管理、擴展AJAX請求,分別是:

  • 前置過濾器 jQuery. ajaxPrefilter
  • 請求分發器 jQuery. ajaxTransport,
  • 類型轉換器 ajaxConvert

除此之后還重寫了整個異步隊列處理,加入了deferred,可以將任務完成的處理方式與任務本身解耦合,使用deferreds對象,多個回調函數可以被綁定在任務完成時執行,甚至可以在任務完成后綁定這些回調函數。這些任務可以是異步的,也可以是同步的。

比如

1.鏈式反饋done與fail

$.ajax({
    url: "script.php",
    type: "POST",
    data: {
        id: menuId
    },
    dataType: "html"
}).done(function(msg) {
    $("#log").html(msg);
}).fail(function(jqXHR, textStatus) {
    alert("Request failed: " + textStatus);
});

 

2.分離異步與同步處理

var aajax = $.ajax({
    url: "script.php",
    type: "POST",
    data: {
        id: menuId
    },
    dataType: "html"
}).fail(function(jqXHR, textStatus) {
    alert("Request failed: " + textStatus);
});


//同步還在執行代碼,這個函數有可能在AJAX結束前調用
dosomething()

//異步還在等在成功響應
aajax.done(function(msg) {
    $("#log").html(msg);
})

不再被限制到只有一個成功,失敗或者完成的回調函數了。相反這些隨時被添加的回調函數被放置在一個先進先出的隊列中。

 

3.同時執行多個ajax請求

function ajax1() {
    return $.get('1.htm');
}

function ajax2() {
    return $.get('2.htm');
}


$.when(ajax1(), ajax2())
    .then(function() {
       //成功
    })
    .fail(function() {
       //失敗
    });

這個比較復雜一點,原理其實就是$.get返回的是一個deferred對象,每個jQuery的AJAX方法返回值都包含一個promise函數,用來跟蹤異步請求。Promise函數的返回值是deferred對象的一個只讀視圖

Deferreds通過檢測對象中是否存在promise()函數來判斷當前對象是否可觀察。$.when()會等待所有的AJAX請求結束,然后調用通過 .then(), .fail()注冊的回調函數(具體調用哪些回調函數取決於任務的結束狀態)。這些回調函數會按照他們的注冊順序執行

顯而易見,deferred對象就是jQuery的回調函數解決方案,它解決了如何處理耗時操作的問題,對那些操作提供了更好的控制,以及統一的編程接口

 


jqXHR 對象

說白了無非就是在ajax的實現邏輯中,把deferred對象給摻進去了,使之整個ajax方法變成了一個deferred對象

在ajax方法中返回的是jqXHR一個包裝對象,在這個對象里面混入了所有實現方法

ajax: function(url, options) {
    //.........
    return jqXHR
}

從jQuery 1.5開始$.ajax() 返回XMLHttpRequest(jqXHR)對象,該對象是瀏覽器的原生的XMLHttpRequest對象的一個超集。例如,它包含responseTextresponseXML屬性,以及一個getResponseHeader()方法。當傳輸機制不是是XMLHttpRequest時(例如,一個JSONP請求腳本,返回一個腳本 tag 時),jqXHR對象盡可能的模擬原生的XHR功能。

從jQuery 1.5.1開始jqXHR對象還包含了overrideMimeType方法 (它在jQuery 1.4.x中是有效的,但是在jQuery 1.5中暫時的被移除)。.overrideMimeType() 方法可能用在beforeSend()的回調函數中,例如,修改響應的Content-Type信息頭:

為了讓回調函數的名字統一,便於在$.ajax()中使用。jqXHR也提供.error() .success().complete()方法。這些方法都帶有一個參數,該參數是一個函數,此函數在 $.ajax()請求結束時被調用,並且這個函數接收的參數,與調用 $.ajax()函數時的參數是一致。這將允許你在一次請求時,對多個回調函數進行賦值,甚至允許你在請求已經完成后,對回調函數進行賦值(如果該請求已經完成,則回調函數會被立刻調用)。

為了向后兼容XMLHttpRequest ,一jqXHR對象將公開下列屬性和方法:

  • readyState
  • status
  • statusText
  • responseXML and/or responseText 當底層的請求分別作出XML和/或文本響應
  • setRequestHeader(name, value) 從標准出發,通過替換舊的值為新的值,而不是替換的新值到舊值
  • getAllResponseHeaders()
  • getResponseHeader()
  • abort()

image

 


那么在對包裝器jqXHR對象做混入之前,我們要先准備好

1 異步隊列deferred

2 回調隊列Callbacks

// Deferreds
deferred = jQuery.Deferred(),

/**
 * 所有的回調隊列,不管任何時候增加的回調保證只觸發一次
 * @type {[type]}
 */
completeDeferred = jQuery.Callbacks("once memory"),

 

給jqXHR擴充添加promise的屬性和方法,然后添加complete方法,這里用的是回調列表的add方法(即添加回調)

deferred.promise(jqXHR).complete = completeDeferred.add;

image

此時的jqXHR就具有了promise的一些特性了與callback的回調列隊了

當然這里有個重點,返回了一個只讀的deferred對象

如果返回完整的deferred對象,那么外部程序就能隨意的觸發deferred對象的回調函數,很有可能在AJAX請求結束前就觸發了回調函數(resolve),這就是與AJAX本身的邏輯相違背了。

所以為了避免不經意間改變任務的內部流程,我們應該只返回deferred的只讀版本 deferred.promise()

然后把對應的done與fail改成別名success與error

jqXHR.success = jqXHR.done;
jqXHR.error   = jqXHR.fail;

 

 

image

 

我們還需要把用戶自定的內部回調函數給注冊到jqXHR對象上

// 增加回調隊列
        for (i in {
            success  : 1,
            error    : 1,
            complete : 1
        }) {
            /**
             * 把參數的回調函數注冊到內部jqXHR對象上,實現統一調用
             * 給ajax對象注冊 回調函數add
             * deferred返回complete,error外部捕獲
             */
            jqXHR[i](s[i]);
        }

jqXHR.success(s.success)  -> jqXHR.done -> jQuery.Callbacks("once memory")

jqXHR.error(s.error)  -> jqXHR.fail -> jQuery.Callbacks("once memory")

jqXHR.complete(s.complete) -> jQuery.Callbacks("once memory").add(s.success)

待續....

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM