Skip to main content
 首页 » 编程设计

JS构建XHR连接队列

2022年07月16日27me-sa
<!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