• 构建XHR连接队列


    <!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>

    另一个版本

  • 相关阅读:
    Sqlite框架Delphi10.3(07)
    FastReport 6.8.11在Delphi10.3上的安装
    Delphi 中,InputQuery 函数的妙用
    Javaday25(sax解析xml,json,设计模式)
    并发学习第五篇——volatile关键字
    并发学习第四篇——synchronized关键字
    并发学习第二篇——Thread
    并发学习第一篇——Runnable
    git-仓库迁移并保留commit log
    并发编程理论基础二——画图理解
  • 原文地址:https://www.cnblogs.com/webFrontDev/p/2888574.html
Copyright © 2020-2023  润新知