根据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