根据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将被赋值为onsuccess
if (typeof oRequest.onsuccess == "function") {
fnCallback = oRequest.onsuceess;
}
} else if (oTransport.status == 304) { // 状态码为304,fnCallback被赋值为onnotmodified
if (typeof oRequest.onnotmodified == "function") {
fnCallback = oRequest.onnotmodified;
}
} else {
if (typeof oRequest.onfailure == "function") { // 其他状态码,fnCallback赋值为onfailure
fnCallback = 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});
}
},
// 针对定期刷新模式,定义其优先级为3
poll : 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);
},
// 针对提交节流模式 ,优先级比较高为2
submitPart : 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