您的位置:首页 > 其它

构建XHR连接队列

2013-02-01 10:17 232 查看
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="utf-8">
<title></title>
<style>
body {
font: 100% georgia, times, serif;
}

h1, h2 {
font-weight: normal;
}

#queue-items {
padding: 0.5em;
background: #ddd;
border: 1px solid #bbb;
}

#results-area {
padding: 0.5em;
border: 1px solid #bbb;
}
</style>
</head>
<body id="example">
<div id="doc">
<h1>Ajax Connection Queue</h1>

<div id="queue-items"></div>
<div id="add-stuff">
<h2>Add Requests to Queue</h2>
<ul id="adders">
<li><a id="action-01" href="">Add "01" to Queue</a></li>
<li><a id="action-02" href="">Add "02" to Queue</a></li>
<li><a id="action-03" href="">Add "03" to Queue</a></li>
</ul>
</div>
<h2>Other Queue Actions</h2>
<ul id="items">
<li><a id="flush" href="">Flush</a></li>
<li><a id="dequeue" href="">Dequeue</a></li>
<li><a id="pause" href="">Pause</a></li>
<li><a id="clear" href="">Clear</a></li>
</ul>
<div id="results-area">
<h2>Results:</h2>

<div id="results"></div>
</div>
</div>

<script src="Library.js"></script>
<script>
var asyncRequest = (function () {
function handleReadyState(o, callback) {
var poll = window.setInterval(function () {
if (o && o.readyState === 4) {
window.clearInterval(poll);
if (callback) {
callback.call(o, o.responseText, o.responseXML);
}
}
}, 50);
}

var getXHR = function () {
var http;
try {
http = new XMLHttpRequest();
getXHR = function () {
return new XMLHttpRequest();
};
} catch (e) {
var msxml = [
'MSXML2.XMLHTTP.3.0',
'MSXML2,XMLHTTP',
'Microsoft.XMLHTTP'
];
for (var i = 0, len = msxml.length; i < len; i++) {
try {
http = new ActiveXObject(msxml[i]);
getXHR = function () {
return new ActiveXObject(getXHR.str);
};
getXHR.str=msxml[i];
break;
} catch (e) {
}
}
}
return http;
};

return function (method, url, callback, postVars) {
var http = getXHR();
handleReadyState(http, callback);
http.open(method, url, true);
http.send(postVars || null);
}
})();

Function.prototype.method = function (name, fn) {
this.prototype[name] = fn;
return this;
};

if (!Array.prototype.forEach) {
Array.method('forEach', function (fn, thisObj) {
var scope = thisObj || window;
for (var i = 0, len = this.length; i < len; i++) {
fn.call(scope, this[i], i, this);
}
});
}

if (!Array.prototype.filter) {
Array.method('filter', function (fn, thisObj) {
var scope = thisObj || window;
var a = [];
for (var i = 0, len = this.length; o < len; i++) {
if (!fn.call(scope, this[i], i, this)) {
continue;
}
a.push(this[i]);
}
return a;
});
}

window.DED = window.DED || {};
DED.util = DED.util || {};
DED.util.Observer = function () {
this.fns = [];
};
DED.util.Observer.prototype = {
subscribe: function (fn) {
this.fns.push(fn);
},
unsubscribe: function (fn) {
this.fns = this.fns.filter(function (el) {
if (el !== fn) {
return el;
}
});
},
fire: function (o) {
this.fns.forEach(function (el) {
el(o);
});
}
};

DED.Queue = function () {
this.queue = [];
this.onComplete = new DED.util.Observer();
this.onFailure = new DED.util.Observer();
this.onFlush = new DED.util.Observer();
this.retryCount = 3;
this.currentRetry = 0;
this.paused = false;
this.timeout = 5000;
this.conn = {};
this.timer = {};
};
DED.Queue.method('flush',function () {
if (!this.queue.length > 0) {
return;
}
if (this.paused) {
this.paused = false;
return;
}
var that = this;
this.currentRetry++;
var abort = function () {
that.conn.abort();
if (that.currentRetry === that.retryCount) {
that.onFailure.fire();
that.currentRetry = 0;
} else {
that.flush();
}
};
this.timer = window.setTimeout(abort, that.timeout);
var callback = function (o) {
window.clearInterval(that.timer);
that.currentRetry = 0;
that.queue.shift();
that.onFlush.fire(o);
if (that.queue.length === 0) {
that.onComplete.fire();
return;
}
that.flush();
};
this.conn = asyncRequest(
this.queue[0]['method'],
this.queue[0]['url'],
callback,
this.queue[0]['param']
);
}).method('setRetryCount',function (count) {
this.retryCount = count;
}).method('setTimeout',function (time) {
this.timeout = time;
}).method('add',function (o) {
this.queue.push(o);
}).method('pause',function () {
this.paused = true;
}).method('dequeue',function () {
this.queue.pop();
}).method('clear', function () {
this.queue = [];
});
</script>
<script>
addEvent(window, 'load', function () {
var q = new DED.Queue();
q.setRetryCount(5);
q.setTimeout(3000);
var items = $('items'),
results = $('results'),
queue = $('queue-items'),
requests = [];
q.onFlush.subscribe(function (data) {
results.innerHTML = data;
requests.shift();
queue.innerHTML = requests.toString();
});
q.onFailure.subscribe(function () {
results.innerHTML += '<span style="color:red;">Connection Error.</span>';
});
q.onComplete.subscribe(function () {
results.innerHTML += '<span style="color:green;">Completed</span>';
});
var actionDispatcher = function (element) {
switch (element) {
case 'flush':
q.flush();
break;
case 'dequeue':
q.dequeue();
requests.pop();
queue.innerHTML = requests.toString();
break;
case 'pause':
q.pause();
break;
case 'clear':
q.clear();
requests = [];
queue.innerHTML = '';
break;
default:
break;
}
};
var addRequest = function (data) {
q.add({
method: 'GET',
url: 'main.js?ajax=true&s=' + data,
params: null
});
requests.push(data);
queue.innerHTML = requests.toString();
};

addEvent(items, 'click', function (e) {
e = e || window.event;
var src = e.target || e.srcElement;
try {
e.preventDefault();
} catch (e) {
e.returnValue = false;
}
actionDispatcher(src.id);
});

var adders = $('adders');
addEvent(adders, 'click', function (e) {
e = e || window.event;
var src = e.target || e.srcElement;
try {
e.preventDefault();
} catch (ex) {
e.returnValue = false;
}
addRequest(src.id.split('-')[1]);
});
});


/**
* 检查JSON文本,确保安全
* @param {String} s JSON字符串
* @param {Function} filter 过滤方法
* @return {*}
*/
function parseJSON(s, filter) {
var j;

/**
* 递归地遍历了新生成的结构
而且将每个名/值对传递给一个过滤函数,以便进行
可能的转换
* @param k
* @param v
* @return {*}
*/
function walk(k, v) {
if (v && typeof v === 'object') {
for (var i in v) {
if (v.hasOwnProperty(i)) {
v[i] = walk(i, v[i]);
}
}
}
return filter(k, v);
}

/*
解析通过3个阶段进行。第一阶段,通过正则表达式
检测JSON文本,查找非JSON字符串。其中,特别关注
“()”和"new",因为它们会引起语句的调用,还有“=”,
因为它会导致变量的值发生改变。不过,为安全起见
这里会拒绝所有不希望出现的字符串
*/
/*
首先这个串分成两部分,看中间的或符号(|)
"(\\.|[^\\\n\r])*?"和[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
先分解"(\\.|[^\\\n\r])*?"
它匹配一个双引号的字符串,两边引号不说了括号内一个“|”又分成两段 “\\.“匹配一个转义字符
比如js字符串里的\n,\r,\',\"等。[^\\\n\r]匹配一个非\,回车换行的字符 其实它就是js里字符串的规则---不包含回车换行,回车换行用 \n\r表示,\后面跟一个字符表示转义
其次看[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
它匹配一个单个字符,这个字符可以是 ,,:,{,},[,],数字,除 "\n" 之外的任何单个字符,-,+,E,a,e,f,l,n,r-u之间的字符,回车,换行,制表符,
*/
if (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(s)) {
/*
第二阶段,使用eval()函数将JSON文本编译为js
结构。其中的“{”操作符具有语法上的二义性,即它可
以定义一个语句块,也可以表示对象字面量。这里将
JSON文本用括号括起来是为了消除这种二义性
*/
try {
j = eval('(' + s + ')');
} catch (e) {
throw new SyntaxError('parseJSON');
}
} else {
throw new SyntaxError('parseJSON');
}

/*
在可选的第三阶段,代码递归地遍历了新生成的结构
而且将每个名/值对传递给一个过滤函数,以便进行
可能的转换
*/
if (typeof filter === 'function') {
j = walk('', j);
}
return j;
}

/**
* 设置XMLHttpRequest对象的各个不同的部分
* @param url
* @param options
*      参数options的成员属性
*      method, 适用于请求的方法,默认为GET。
*      send, 是一个包含在XMLHttpRequest.send()中的可选字符串,默认为null。
*      loadListener, 是当readyState为1时调用的onreadystatechange侦听器
*      loadedListener, 是当readyState为2时调用的onreadystatechange侦听器。
*      interactiveListener, 是当readyState为3时调用的onreadystatechange侦听器。
*      jsResponseListener, 是当请求成功并且响应的Content-Type为application/javascript或text/javascript时调用的侦听器,这个侦听器将从响应中取得js字符串作为其第一个参数。如果要执行该js字符串,必须使用eval()方法。
*      jsonResponseListener, 是当请求成功并且响应的Content-Type为application/json时调用的侦听器。这个侦听器将从响应中取得JSON对象作为其第一个参数。
*      xmlResponseListener, 是当请求成功并且响应的Content-Type为application/xml或application/xhtml+xml时调用的侦听器。这个侦听器将从响应中取得的XML DOM文档作为其第一个参数。
*      htmlResponseListener, 是当请求成功并且响应的content-Type为text/html时调用的侦听器。这个侦听器将从响应中取得的HTML字符串作为其第一个参数。
*      completeListener, 是当上面所列的针对Content-Type的响应侦听器调用之后被调用的侦听器。这个方法总是在成功的响应最后被调用,也就是说如果相应中没有适当的Content-Type头部信息,那么你可以制定这个方法作为兜底儿的侦听器。
*      errorListener, 是当响应状态值不是200也不是0时被调用的侦听器。如果是在不会提供适当响应代码的系统上运行(如硬盘驱动器中的本地文件系统)XMLHttpRequest,那么状态值将始终为0.在这种情况下,只有completeListener会被调用。
*/
/*
demo:
ADS.ajaxRequest('/path/to/script/',{
method:'GET',
completeListener:function(){
alert(this.responseText);
}
});
*/
// 因为使用了call和apply方法,此时的this引用的是
// 请求对象而不是onreadystatechange方法。
function getRequestObject(url, options) {
// 初始化请求对象
var req = false;
if (window.XMLHttpRequest) {
req = new window.XMLHttpRequest();
} else if (window.ActiveXObject) {
req = new ActiveXObject('Microsoft.XMLHTTP');
}

if (!req) {
return false;
}

// 定义默认的选项
options = options || {};
options.method = options.method || 'GET';
options.send = options.send || null;

// 为请求的每个阶段定义不同的侦听器
req.onreadystatechange = function () {
switch (req.readyState) {
case 1:
// 载入中
if (options.loadListener) {
options.loadListener.apply(req, arguments);
}
break;
case 2:
// 载入完成
if (options.loadedListener) {
options.loadedListener.apply(req, arguments);
}
break;
case 3:
// 交互
if (options.interactiveListener) {
options.interactiveListener.apply(req, arguments);
}
break;
case 4:
// 完成
// 如果失败则抛出错误
try {
if (req.status && req.status === 200) {
// 针对Content-type的特殊侦听器
// 由于Content-Type头部中可能包含字符集,如:
// Content-Type: text/html; charset=ISO-8859-4
// 因此通过正则表达式提取出所需的部分
var contentType = req.getResponseHeader('Content-Type');
var mimeType = contentType.match(/\s*([^;]+)\s*(;|$)/i)[1];
switch (mimeType) {
case 'text/javascript':
case 'application/javascript':
// 响应时javascript,因此以
// req.responseText作为回调函数
if (options.jsResponseListener) {
options.jsResponseListener.call(req, req.responseText);
}
break;
case 'application/json':
// 响应是JSON,因此需要用匿名函数对
// req.responseText进行解析
// 已返回作为回调参数的JSON对象
if (options.jsonResponseListener) {
var json;
try {
json = parseJSON(req.responseText);
} catch (e) {
json = false;
}
options.jsonResponseListener.call(req, json);
}
break;
case 'text/xml':
case 'application/xml':
case 'application/xhtml+xml':
// 响应是XML,因此以
// req.responseXML作为
// 回调的参数
// 此时是Document对象
if (options.xmlResponseListener) {
options.xmlResponseListener.call(req, req.responseText);
}
break;
case 'text/html':
// 响应是HTML,因此以
// req.responseText作为
// 回调的参数
if (options.htmlResponseListener) {
options.htmlResponseListener.call(req, req.responseText);
}
break;
default:
break;
}

// 针对响应成功完成的侦听器
if (options.completeListener) {
options.completeListener.apply(req, arguments);
}
} else {
// 相应完成但却存在错误
if (options.errorListener) {
options.errorListener.apply(req, arguments);
}
}
} catch (e) {
// 忽略错误
}
break;
}
};

// 开启请求
req.open(options.method, url, true);
// 添加特殊的头部信息以标识请求
req.setRequestHeader('X-ADS-Ajax-Request', 'AjaxRequest');
return req;
}

window.ADS.getRequestObject = getRequestObject;

// 通过简单的包装getRequestObject()和send()
// 方法发送XMLHttpRequest对象的请求
function ajaxRequest(url, options) {
var req = getRequestObject(url, options);
return req.send(options.send);
}

window.ADS.ajaxRequest = ajaxRequest;

/* 一个复制对象的辅助方法 */
function clone(myObj) {
if (typeof myObj !== 'object') {
return myObj;
}
if (myObj === null) {
return myObj;
}
var myNewObj = {};
for (var i in myObj) {
myNewObj[i] = clone(myObj[i]);
}
return myNewObj;
}

/* 用于保存队列的数组 */
var requestQueue = [];

/**
* 为ADS.ajaxRequest方法启用排队功能的包装对象
* @param url
* @param options
* @param queue
* @example
*      ADS.ajaxRequestQueue('/your/script/', {
*          completeListener: function(){
*              alert(this.responseText);
*          }
*      }, 'Queue1');
*/
function ajaxRequestQueue(url, options, queue) {
queue = queue || 'default';

// 这个对象将把可选的侦听器包装在另一个函数中
// 因此,可选的对象必须唯一。否则,如果该方法
// 被调用时使用的是共享的可选对象,那么会导致
// 陷入递归中
options = clone(options) || {};
if (!requestQueue[queue]) {
requestQueue[queue] = [];
}

// 当前一次请求完成时,需要使用completeListener
// 调用队列中的下一次请求。如果完成侦听器已经
// 有定义,那么需要首先调用它

// 取得旧侦听器
var userCompleteListener = options.completeListener;

// 添加新侦听器
options.completeListener = function () {
// 如果存在旧的侦听器则首先调用它
if (userCompleteListener) {
// this引用的是情求对象
userCompleteListener.apply(this, arguments);
}

// 从队列中移除这个请求
requestQueue[queue].shift();

// 调用队列中的下一项
if (requestQueue[queue][0]) {
// 请求保存在req属性中,但为防止它是
// 一个POST请求,故也需包含send选项
var q = requestQueue[queue][0].req.send(
requestQueue[queue][0].send
);
}
};

// 如果发生了错误,应该通过调用相应的
// 错误处理方法取消队列中的其他请求

// 取得旧侦听器
var userErrorListener = options.errorListener;

// 添加新侦听器
options.errorListener = function () {
if (userErrorListener) {
userErrorListener.apply(this, arguments);
}

// 由于已经调用了错误侦听器
// 股从队列中移除这个请求
requestQueue[queue].shift();

// 由于出错需要取消队列中的其余请求,但首先要调用
// 每个请求的errorListener。通过调用队列中
// 下一项的错误侦听器就会才清楚所有排队的请求,因为在
// 链中的调研那个是一次发生的

// 检测队列中是否还存在请求
if (requestQueue[queue].length) {
// 取得下一项
var q = requestQueue[queue].shift();

// 中断请求
q.req.abort();

// 伪造请求对象,以便errorListener
// 认为请求已经完成并相应地运行

var fakeRequest = {};

// 将status设置为0,将readyState设置为4
// 就好像请求虽然完成但却失败了一样
fakeRequest.status = 0;
fakeRequest.readyState = 4;

fakeRequest.responseText = null;
fakeRequest.responseXML = null;

// 设置错误信息,以便需要时显示
fakeRequest.statusText = 'A request in the queue received an error';

// 调用状态改变,如果readyState是4,而
// status不是200,则会调用errorListener
q.error.apply(fakeRequest);
}
};

// 将这个请求添加到队列中
requestQueue[queue].push({
req: getRequestObject(url, options),
send: options.send,
error: options.errorListener
});

// 如果队列的长度表明只有一个
// 项(即第一个)则调用请求
if (requestQueue[queue].length === 1) {
ajaxRequest(url, options);
}
}

window.ADS.ajaxRequestQueue = ajaxRequestQueue;




</script>
</body>
</html>


另一个版本
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: