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物件的一個超集。例如,它包含responseText
和responseXML
屬性,以及一個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/orresponseText
當底層的請求分別作出XML和/或文字響應setRequestHeader(name, value)
從標準出發,通過替換舊的值為新的值,而不是替換的新值到舊值getAllResponseHeaders()
getResponseHeader()
abort()
那麼在對包裝器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;
此時的jqXHR就具有了promise的一些特性了與callback的回撥列隊了
當然這裡有個重點,返回了一個只讀的deferred物件
如果返回完整的deferred物件,那麼外部程式就能隨意的觸發deferred物件的回撥函式,很有可能在AJAX請求結束前就觸發了回撥函式(resolve),這就是與AJAX本身的邏輯相違背了。
所以為了避免不經意間改變任務的內部流程,我們應該只返回deferred的只讀版本 deferred.promise()
然後把對應的done與fail改成別名success與error
jqXHR.success = jqXHR.done;
jqXHR.error = jqXHR.fail;
我們還需要把使用者自定的內部回撥函式給註冊到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)
待續....