根據HTTP1.1的規范,一個客戶端在同一時刻與同一域名不能有兩個以上的連接。為了完全符合HTTP1.1,一個典型的解決方案就是使用優先級隊列.下面是自定義的優先級隊列
/**
* 用原型模式定義PriorityQueue的方法,
* 如果沒有定義_compare()方法,那么第一個方法就是默認的_compare()方法.
* 由于方法并不是作為公有屬性訪問的,因此實現一個prioritize()來使用該方法
*/ PriorityQueue.prototype = {// _compare()方法只是最基本的比較函數,它基于每個項的原始值來確定哪個項應該排在前面._compare : function (oValue1, oValue2) {if (oValue1 < oValue2) {return -1;} else if (oValue1 > oValue2) {return 1;} else {return 0;}},// 當把新項添加到隊列中時,將調用prioritize()方法來保證這些項按正確的順序排列。對于prioritize : function () {this._items.sort(this._compare);},// 對于優先級隊列而言,有5個方法來完成其基本操作:get()、item()、peek()、put()和size()// get()方法用來返回隊列中指定位置的項get : function() {return this._items.shift();},// item()方法用來返回隊列中指定位置的項。item : function (iPos) {return this._items[iPos];},// peek()方法用來獲取隊列中的下一個項,但不從隊列中刪除(只是查看下一項的值)peek : function () {return this._items[0];}// put()方法負責將新的值添加到隊列中put : function (oValue) {this._items.push(oValue);this.prioritize();},// size()方法將返回隊列中項的總數size : function () {return this._items.length;}// PriorityQueue對象的最后一個方法是remove(),它將再隊列中搜素指定的值,然后將其刪除.remove : function (oValue) {for (var i=0; i < this._items.length; i++) {if (this._items[i] === oValue) {this._items.splice(i, 1);return true;}}return false;
};
上面我們自定義了一個優先級隊列函數PriorityQueue ,它的作用是對XHR請求按照優先級進行排序(若傳入了排序的規則,則按排序的規則,否則按照默認的規則)
有了PriorityQueue之后,下面還需要了解一下請求描述對象(了解一下每個字段什么意思)
var oRequest = {priority: 1, // 優先級type: "post", // 請求方式url: , // 請求路徑data: "post_data",oncancel: function () {}, // 取消執行的函數onsuccess: function () {}, // 成功執行的函數onnotmodified: function () {}, // 服務器端請求數據未更新執行的函數onfailure: function () {}, // 請求失敗執行的函數scope: Object // 該函數調用的作用域,默認是全局
在了解了請求描述對象之后,下面可以開始對請求進行排隊了
var RequestManager = (function () {var oManager = {AGE_LIMIT : 60* 1000 , // 最大等待時間.DEFAULT_PRIORITY: 10, // 默認優先級_active: new Array(), // 后面會用到,// _pending()方法:用于比較的函數// oRequest1和oRequest2是XHR的**請求描述對象**_pending : new PriorityQueue(function (oRequest1, oRequest2) {return oRequest1.priority - pRequest2.priority;}),// 上面已經自定義了優先級隊列PriorityQueue,并且將XHR請求按照優先級排進了PriorityQueue中,接下來是發送請求.// 在發送請求之前,我們需要兼容的創建XHR方法_createTransport : function () {if (typeof XMLHttpRequest != "undefined") {return new XMLHttpRequest();} else if (typeof ActiveXObject ! = "undefined") {var oHttp = null;try {oHttp = new ActiveXObject("MSXML2.XmlHttp.6.0");return oHttp;} catch (oEx) {try {pHttp = new ActiveXObject("MSXML2.XmlHttp.3.0");return oHttp;} catch (oEx2) {throw Error ("Cannot create XMLHttp object.");}}}},// 上面方法已經可以創建合適的XHR對象了,下面該發送請求了_sendNext : function () {if (this._active.length < 2) {var oRequest = this._pending.get();if (oRequest != null) {this._active.push(oRequest);oRequest.transport = this._createTransport();oRequest.transport.open(oRequest.type, oRequest.url, true);oRequest.transport.send(oRequest.data);oRequest.active = true;}}},/***監控請求,檢查每個活動請求的狀態*/ _checkActiveRerquests : function () {var oRequest = null;var oTransport = null;// 使用一個for循環來遍歷active數組中的每個請求,(由于請求可能被刪除,因此這個循環以反向順序來檢查避免遺漏某個請求)for (var i=this._active.length-1; i >=0; i--) {oRequest = this._active[i]; // 保留每個請求oTransport =oRequest.transport;if (oTransport.readyState == 4) { // 檢查,若狀態為4則進一步檢查oRequest.active = false; // 將active屬性置為false,說明該請求已經被返回并已完成this._active.splice(i, 1);var fnCallback = null;if (oTransport.status >= 200 && oTransport.status < 300) { // 狀態碼在200~299之間,變量fnCallback將被賦值為onsuccessif (typeof oRequest.onsuccess == "function") {fnCallback = oRequest.onsuceess;}} else if (oTransport.status == 304) { // 狀態碼為304,fnCallback被賦值為onnotmodifiedif (typeof oRequest.onnotmodified == "function") {fnCallback = oRequest.onnotmodified;}} else {if (typeof oRequest.onfailure == "function") { // 其他狀態碼,fnCallback賦值為onfailurefnCallback = oRequest.onfailure;}}// 檢查fnCallback函數是否為一個有效函數,若有效則設置一個延遲函數去執行它,設置延遲可以確保輪詢數能在回調函數執行前執行完畢.if (fnCallback != null) { /*** 為了確保在正確的作用域內執行,將在需要的時候實時創建一個傳給setTimeout()函數的函數.* 這個匿名函數有3個參數,以便為每個變量創建正確的副本*/setTimeout((function (fnCallback, oRequest, oTransport) {return function () {fnCallback.call(oRequest.scope ||window, {status: oTransport.status,data: oTransport.responseText,request: oRequest});}}) (fnCallback, oRequest, oTransport), 1);}}}},// 上面的優先級策略存在一個風險,優先級低的有可能永遠不會執行// 解決辦法是,設置一個固定的時間,將隊列中的請求描述對象的優先級提升一級_agePromote : function() {for (var i=0; i < this._pending.size(); i++) {var oRequest = this._pending.item(i);oRequest.age += this.INTERVAL;if (oRequest.age >= this.AGE_LIMIT) {oRequest.age =0;oRequset.priority--;}}this._pending.prioritize();},// 在請求被執行之前,有可能需要取消它。通過cancel()方法,可以從請求隊列中刪除請求描述對象cancel : function (oRequest) {if (!this._pending.remove(oRequest)) {oRequest.transport.abort();if (this._active[0] === oRequest) {this._active.shift();} else if (this._active[1] === oRequest) {this._active.pop();}if (typeof oRequest.oncancel == "function") {oRequest.oncancel.call(oRequest.scope || window,{request : oRequest});}},// 針對定期刷新模式,定義其優先級為3poll : function (oRequest) {oRequest.priority = 3;this.send(oRequest);},// 針對預先獲取和多階段下載模式prefetch : function (oRequest) {oRequest.priority = 5;this.send(oRequest);}// send()是將請求添加到隊列中的公有方法// 首先檢查是否是有效的優先級,如果不是則使用默認的優先級(10,最低).send : function (oRequest) {if(typeof oRequest.priority != "number"){oRequest.priority = this.DEFAULT_PRIORITY;}oRequest.active = false; // 用來確定當前請求是否在執行oRequest.age = 0;this._pending.put(oRequest);},// 針對用戶操作,優先級最高submit : function (oRequest) {oRequest.priority = 0;this.send(oRequest);},// 針對提交節流模式 ,優先級比較高為2submitPart : function (oRequest) {oRequest.priority = 2;this.send(oRequest);}
};// 啟動
setInterval (function () {RequestManager._checkActiveRequests();RequestManager._sendNext();RequestManager._agePromote();
}, oManager.INTERVAL);//返回該對象return oManager;
}) ();
// 使用上面定義的方法
RequestManager.poll({type: "get",url: "example.html",data: ""post_data",onsuccess: function() {},
});RequestManager.submitPart({type:"post",url: "handler.html",data: "name=Nicholas",onsuccess: function() {},
});
參考《Ajax高級程序設計》(第二版) P110~P126