JavaScript --- > 自定义优先级队列

根据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

猜你喜欢

转载自blog.csdn.net/piano9425/article/details/88756861