返回列表

前端MVC框架[02] 发送AJAX请求及[建立连接池]

默认分类 2012-11-06 18:29:17

/**
 * Requester请求管理器
 */

var Requester = {
    /**
     * 全局事件处理接口 注:不支持onsuccess
     *
     * @Map {'ontimeout':function(){},'onfailure':function(){}}
     */
    handler:{},
    /** 
     * 创建XMLHttpRequest对象 
     *
     * @return {XMLHttpRequest} XMLHttpRequest对象 
     * @description 使用缓存模式避免每次都检测浏览器类型
     */ 
    createOriginalXHRObject: function () {
        var me = this,
            i,
            list,
            len,
            xhr = null,
            methods = [
                function () {return new XMLHttpRequest();}, 
                function () {return new ActiveXObject('Msxml2.XMLHTTP');},
                function () {return new ActiveXObject('Microsoft.XMLHTTP');}
            ];
            
        for (i = 0, len = methods.length; i < len; i++) {
            try {
                xhr = methods[i]();
                this.createOriginalXHRObject = methods[i];
                break;
            } catch (e) {
                continue;
            }
        }
        if (!xhr) {
            throw new Error(100000,'Requester.createXHRObject() fail. Your browser not support XHR.');
        }
        
        return xhr;
    },
    /** 
     * 预置XMLHttpRequest对象 
     *
     * @return {XMLHttpRequest} XMLHttpRequest对象 
     * @description 
     */ 
    createXHRObject: function () {            
        var me = this,
            xhr = me.createOriginalXHRObject();
        xhr.eventHandlers = {};
        xhr.fire = me.creatFireHandler();
        
        return xhr;
    },
    /** 
     * 生成新的触发事件方法 
     *
     * @param {String} type 事件类型 
     */ 
    creatFireHandler: function(){
        return function (type) { 
            type = 'on' + type; 
            var xhr = this,
                handler = xhr.eventHandlers[type], 
                globelHandler = window.Requester.handler[type]; 
                
            // 不对事件类型进行验证 
            if (handler) { 
                if (xhr.tick) { 
                  clearTimeout(tick); 
                } 

                if (type != 'onsuccess') { 
                    handler(xhr); 
                } else { 
                    //处理获取xhr.responseText导致出错的情况,比如请求图片地址. 
                    try { 
                        xhr.responseText; 
                    } catch(error) { 
                        return handler(xhr); 
                    } 
                    var text = xhr.responseText.replace(/^\s+/ig, ""); 
                    if(text.indexOf('{') === 0){ 
                        //{success:true,message: 
                        //插入表单验证错误提示 
                        var JSONParser; 
                        try { 
                            JSONParser = new Function("return " + text + ";");
                            data = JSONParser();
                        } 
                        //如果json解析出错则尝试移除多于逗号再试 
                        catch (e){ 
                            JSONParser = new Function("return " + window.Requester.removeJSONExtComma(text) + ";"); 
                            data = JSONParser();
                        } 
                        if ( String(data.success).replace(/\s/ig,'').toLowerCase() !== 'true' ) { 
                            // 当后端验证失败时
                            if (Requester.backendError && xhr.eventHandlers['action']) {
                                Requester.backendError(xhr, data);
                            }
                        } 
                        
                        handler(data); 
                    }else{ 
                        handler(text); 
                    } 
                } 
            } 
            // 检查是否配置了全局事件
            else if (globelHandler) { 
                //onsuccess不支持全局事件 
                if (type == 'onsuccess') { 
                    return; 
                } 
                globelHandler(xhr); 
            }
        };
    },
    /**
     * 检测是否有空闲的XHR或创建新对象
     *
     * @after Requester
     * @comment 使用Facade外观模式修改Requester.request方法
     * 以增加路径权限判断
     */
    getValidXHR: function () {
        var me = this;
        return me.createXHRObject();
    },
    /**
     * request发送请求
     *
     * @url {String} 请求的URL
     * @options {Map} POST的参数,回调函数,MD5加密等
     */ 
    request: function (url, opt_options) {
        var xhr = this.getValidXHR();
        //有可用连接
        if (xhr) {
            var me = this,
                options     = opt_options || {}, 
                data        = options.data || "", 
                async       = !(options.async === false), 
                username    = options.username || "", 
                password    = options.password || "", 
                method      = (options.method || "GET").toUpperCase(), 
                headers     = options.headers || {}, 
                // 基本的逻辑来自lili同学提供的patch 
                timeout     = options.timeout || 0, 
                usemd5      = options.usemd5 || false,
                tick, key, str,
                stateChangeHandler = me.createStateChangeHandler(); 
                
            // 将options参数中的事件参数复制到eventHandlers对象中 
            // 这里复制所有options的成员,eventHandlers有冗余 
            // 但是不会产生任何影响,并且代码紧凑
            for (key in options) { 
                try {
                   xhr.eventHandlers[key] = options[key]; 
                }
                catch (e) {
                    alert(1);
                } 
            } 
            xhr.url = url;
            
            headers['X-Requested-With'] = 'XMLHttpRequest'; 
            
            try { 
                //提交到服务器端的参数是Map则转换为string
                if(Object.prototype.toString.call(data)==='[object Object]'){ 
                    str = [] 
                    for(key in data){
                        if (key){
                            str.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key])) 
                        }
                    }
                    data = str.join('&');
                }
                
                //使用GET方式提交
                if (method == 'GET') { 
                    if (data) { 
                        url += (url.indexOf('?') >= 0 ? ( data.substr(0,1) == '&' ? '' : '&') : '?') + data; 
                        data = null; 
                    }
                }
                else if (usemd5) {
                    data = window.Requester.encodeMD5(data);
                }
                
                if (username) { 
                    xhr.open(method, url, async, username, password); 
                } else { 
                    xhr.open(method, url, async); 
                } 
                
                if (async) { 
                    xhr.onreadystatechange = stateChangeHandler; 
                } 
                
                // 在open之后再进行http请求头设定 
                // FIXME 是否需要添加; charset=UTF-8呢 
                if (method == 'POST') { 
                    xhr.setRequestHeader("Content-Type", 
                        (headers['Content-Type'] || "application/x-www-form-urlencoded")); 
                } 
                
                for (key in headers) { 
                    if (headers.hasOwnProperty(key)) { 
                        xhr.setRequestHeader(key, headers[key]); 
                    } 
                } 
                
                xhr.fire('beforerequest'); 
                
                if (timeout) { 
                  xhr.tick = setTimeout(function(){ 
                    xhr.onreadystatechange = window.Requester.fn; 
                    xhr.abort(); 
                    xhr.fire("timeout"); 
                  }, timeout); 
                } 
                xhr.send(data); 
                
                if (!xhr.eventHandlers['async']) { 
                    stateChangeHandler(); 
                } 
            } catch (ex) { 
                xhr.fire('failure'); 
            } 
        }
        //暂时没有可用连接,将请求放进队列
        else {
            this.que.push({'url':url,'options':options});
        }
    },
    /** 
     * readyState发生变更时调用 
     *
     * @ignore 
     */ 
    createStateChangeHandler: function() { 
        return function() {
            var xhr = this;
            if (xhr.readyState == 4) { 
                try { 
                    var stat = xhr.status; 
                } catch (ex) { 
                    // 在请求时,如果网络中断,Firefox会无法取得status 
                    xhr.fire('failure'); 
                    return; 
                } 
                
                xhr.fire(stat); 
                
                // http://www.never-online.net/blog/article.asp?id=261 
                    // case 12002: // Server timeout 
                    // case 12029: // dropped connections 
                    // case 12030: // dropped connections 
                    // case 12031: // dropped connections 
                    // case 12152: // closed by server 
                    // case 13030: // status and statusText are unavailable 
                    
                // IE error sometimes returns 1223 when it
                // should be 204, so treat it as success 
                if ((stat >= 200 && stat < 300) 
                    || stat == 304 
                    || stat == 1223) { 
                    xhr.fire('success'); 
                } else { 
                    if (stat === 0 && window.console && window.console.log) {
                        window.console.error('XHR Error: Cross domain, cannot access: %s.',xhr.url);
                    }
                    xhr.fire('failure'); 
                } 
                
                /* 
                 * NOTE: Testing discovered that for some bizarre reason, on Mozilla, the 
                 * JavaScript XmlHttpRequest.onreadystatechange handler 
                 * function maybe still be called after it is deleted. The theory is that the 
                 * callback is cached somewhere. Setting it to null or an empty function does 
                 * seem to work properly, though. 
                 *
                 * On IE, there are two problems: Setting onreadystatechange to null (as 
                 * opposed to an empty function) sometimes throws an exception. With 
                 * particular (rare) versions of jscript.dll, setting onreadystatechange from 
                 * within onreadystatechange causes a crash. Setting it from within a timeout 
                 * fixes this bug (see issue 1610). 
                 *
                 * End result: *always* set onreadystatechange to an empty function (never to 
                 * null). Never set onreadystatechange from within onreadystatechange (always 
                 * in a setTimeout()). 
                 */ 
                window.setTimeout(function() { 
                    // 避免内存泄露. 
                    // 由new Function改成不含此作用域链的 window.Requester.fn 函数, 
                    // 以避免作用域链带来的隐性循环引用导致的IE下内存泄露. By rocy 2011-01-05 . 
                    xhr.onreadystatechange = window.Requester.fn; 
                    if (xhr.eventHandlers['async']) { 
                        xhr = null; 
                    } 
                }, 0); 
                
                window.Requester.checkQue();
            } 
        }
    },
    /**
     * encodeMD5加密提交的数据
     *
     * @data {String} 需要加密的paramString
     * @return {String} 加密后的paramString
     */ 
    encodeMD5: function (data) {
        var paramstr = Base64.encode(data).replace(/\+/g,'*');
        var md5 = String(MD5.encode(paramstr)).toUpperCase();
        paramstr = paramstr.split('');
        paramstr.reverse();

        return 'result=' + md5 + paramstr.join('');
    }
};

window.Requester = Requester;
window.Requester.fn = function(){};

/**
 * 移除JSON字符串中多余的逗号如{'a':[',],}',],}
 *
 * @param {string} JSON字符串
 * @return {string} 处理后的JSON字符串
 */
Requester.removeJSONExtComma = function(str) {
    var i,
        j,
        len,
        list,
        c,
        notValue = null,
        preQuot = null,
        lineNum;

    list = String(str).split('');
    for (i = 0, len = list.length; i < len; i++) {
        c = list[i];
        //单引或双引
        if (/^[\'\"]$/.test(c)) {
            if (notValue === null && preQuot === null) {
                notValue = false;
                preQuot = i;
                continue;
            }
            //值
            if (!notValue) {
                //前面反斜杠个数
                lineNum = 0;
                for (j = i - 1; j > -1; j--) {
                    if (list[j] === '\\') {lineNum++;}
                    else { j = -1; }
                }
                //个数为偶数且和开始引号相同
                //结束引号
                if (lineNum % 2 === 0) {
                    if (list[preQuot] === c) {
                        notValue = true;
                        preQuot = -1;
                    }
                }
            }
            //非值
            else {
                //开始引号
                if (preQuot == -1) {
                    preQuot = i;
                    notValue = false;
                }
                //结束引号
                else if (list[preQuot] === c) {
                    notValue = true;
                    preQuot = -1;
                }
            }
        }
        //逗号
        else if (c === ']' || c === '}') {
            //非值
            if (notValue) {
                for (j = i - 1; j > -1; j--) {
                    if (/^[\t\r
\s ]+$/.test(list[j])) {continue;}
                    else { if (list[j] === ',') list[j] = ''; break; }
                }
            }
        }
    }
    return list.join('').replace(/
/g,'').replace(/\r/g,'');
};

/**
 * 发送Requester请求
 * @function
 * @grammar Requester.get(url, data[, onsuccess])
 * @param {string}     url         发送请求的url地址
 * @param {string}     data         发送的数据
 * @param {Function} [onsuccess] 请求成功之后的回调函数,function(XMLHttpRequest xhr, string responseText)
 * @meta standard
 * @see Requester.request
 * 
 * @returns {XMLHttpRequest}     发送请求的XMLHttpRequest对象
 */
Requester.get     = function (url, data, onsuccess, action, async) {return Requester.request(url, {'onsuccess': onsuccess,'method': 'GET','data': data,'action': action,'async': async});};
Requester.head    = function (url, data, onsuccess, action, async) {return Requester.request(url, {'onsuccess': onsuccess,'method': 'HEAD','data': data,'action': action,'async': async});};
Requester.post    = function (url, data, onsuccess, action, async) {return Requester.request(url, {'onsuccess': onsuccess,'method': 'POST','data': data,'action': action,'async': async});};
Requester.postMD5 = function (url, data, onsuccess, action, async) {return Requester.request(url, {'onsuccess': onsuccess,'method': 'POST','data': data,'action': action,'async': async,'usemd5': true});};

/*============================================
 * 请求返回自动校验
 ============================================*/
/**
 * 当后端验证失败时自动调用
 *
 * @data {Map} XHR返回的responseText
 * @return {void}
 */
Requester.backendError = function (xhr, data) {
    if (window.bui && bui.Control && bui.Control.getByFormName){
        var errorMap = data.message.field, 
            key, input,formMap={}; 
 
        for (key in errorMap) { 
            input = bui.Control.getByFormName(key, xhr.eventHandlers['action']); 
 
            if (input) { 
                //input.errorMessage = errorMap[key]; 
                //UIManager.validate(input, 'backendError,this'); 
                //input.errorMessage = null; 
                hideError(input.main); 
                showError(input.main,errorMap[key]); 
            } 
        } 
    }
};


/*============================================
 * Requester扩展 - XHR请求池
 ============================================*/
Requester.pool =  [];
Requester.poolsize = 10;
/**
 * 来不及执行的XHR请求队列
 *
 * @after Requester
 */
Requester.que = [];
/**
 * checkQue检查队列是否有等待的任务
 *
 * @return {void} 
 */ 
Requester.checkQue = function () {
    var me = this,
        req = me.que.pop();
    if (req && req.url && req.options) {
        window.Requester.request(req.url, req.options);
    }
};
/**
 * 检测是否有空闲的XHR或创建新对象
 *
 * @after Requester
 * @comment 使用Facade外观模式修改Requester.request方法
 * 以增加路径权限判断
 */
Requester.getValidXHR = function () {
    var me = this,
        i,
        list,
        len,
        xhr = null;
    //找出空闲XHR对象
    for (i = 0, len = me.pool.length; i < len; i++) {
        if (me.pool[i].readyState == 0 || me.pool[i].readyState == 4) {
            xhr = me.pool[i];
            break;
        }
    }
    //假如没有空闲对象且请求池未满,则继续新建
    if (xhr == null && me.pool.length < me.poolsize) {
        xhr = me.createXHRObject();
        me.pool.push(xhr);
    }

    return xhr;
};


/**
* 测试代码
*/
//test.json -> {"message":{},"success":"true","result":[]}
Requester.get('ajax/test.json','',function(data){
    alert(data.success)
});
//注: 跨域会导致请求出错
//Requester.get('http://www.5imemo.com/other/ajax/jsonp.php','',function(data){alert(data.success)});