<!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>
另一个版本
本文参考链接:https://www.cnblogs.com/webFrontDev/archive/2013/02/01/2888574.html