这部分我们继续增强我们的系统,如强制定的回调函数。参考一些类库,这些回调函数暂定如下几种:
- onStart:请求开始时调用。
- onSuccess:请求成功时调用,通常后台会返回一个JSON给它做参数。
- onTimeout:请求超时时调用,有一个参数,标识其耗时多少毫秒。
- onError:请求失败时调用,有一个异常对象做参数。如果超时,我们会自动抛出一个自定义错误。
- onComplete:请求完成时调用,为了保证它只执行一次,我们会对它进行包装。
我们需要一些工具函数来完成这个系统,如类型判定,深拷贝,迭代器等。这些我以前在博客也讲过了,不详述了!
var dom = { is:function (obj,type) { return (type === "Null" && obj === null) || (type === "Undefined" && obj === void 0 ) || Object.prototype.toString.call(obj).slice(8,-1) === type; }, noop:function(){}, deepcopy:function(result, source){ for(var key in source) { var copy = source[key]; if(result === copy) continue;//如window.window === window,会陷入死循环,需要处理一下 if(dom.is(copy,"Object")){ result[key] = arguments.callee(result[key] || {}, copy); }else if(dom.is(copy,"Array")){ result[key] = arguments.callee(result[key] || [], copy); }else{ result[key] = copy; } } return result; }, each : function(obj,fn,bind){ for(var i in obj){ if(obj.hasOwnProperty(i)){ if(fn.call(bind || obj[i],obj[i],i,obj)===false){ break } } } }, toQueryString : function (obj) {//将对象转换为查询字符串 var enc = encodeURIComponent, pairs = [], regexp = /%20/g, backstop = {}; dom.each(obj,function(value,name){ if(value != backstop[name]){ var assign = enc(name).replace(regexp,'+') + "="; if(dom.is(value,"Array")){ dom.each(value,function(el){ pairs.push(assign + enc(el).replace(regexp,'+')); }); }else{ pairs.push(assign + enc(value).replace(regexp,'+')); } } }); return pairs.join("&"); // String }, head:document.getElementsByTagName("head")[0], //obj的成员有url,callbackName,callback,data,cache jsonp:function(){} }
然后是系统的主体部分了:
dom.jsonp = function(obj){ var self = arguments.callee; //=============列队处理====================== if (self.callbacks.length) { setTimeout(function() {self(obj)}, 0); return; } //=============配置对象处理=================== obj = dom.deepcopy(dom.deepcopy({}, dom.jsonp.settings), obj); var query = dom.toQueryString(obj.data), url = obj.url+"?"+ query+"&"+obj.callbackName+"=dom.jsonp.callback", script = document.createElement("script"), timed_out = false, timeoutID = null, destroyScript = function(){ script.parentNode && script.parentNode.removeChild( script ); }, old = obj.onComplete; obj.startTime = new Date-0; obj.onComplete = function(){//修正onComplete回调%> if(!arguments.callee.done){ old.call(this,[].slice.call(arguments)) arguments.callee.done = true; } } //========构建新JS文件上的回调函数================== dom.jsonp.addCallback((function(obj){ return function(){//参数未定,第一个参数肯定为JSON var args = [].slice.call(arguments) try{ if (!timed_out){//清除timeout引用 clearTimeout(timeoutID); }else{ throw new Error(504,"Gateway Timeout") } obj.onSuccess.apply(obj,args); }catch(e){ obj.onError.call(null,e); }finally{ obj.onComplete.apply(obj,args); destroyScript(); } } })(obj)); //================设置script标签============= if(!obj.cache) url+= '×tamp='+(new Date-0) script.src = url; dom.head.appendChild(script); obj.onStart.call(obj,null); //===========设置请求超时的相关回调============ if (obj.timeout){ timeoutID = setTimeout(function(){ //如果超时则执行如下回调函数 timed_out = true; var time = new Date - obj.startTime; obj.onTimeout.call(obj,time); obj.onComplete.call(obj,null); destroyScript(); },obj.timeout) } }; //静态成员 dom.deepcopy(dom.jsonp, { settings : { url:location.href, callbackName: 'callback', cache:false, onStart:dom.noop, onSuccess:dom.noop, onComplete:dom.noop, onTimeout:dom.noop, onError:dom.noop }, callbacks : [], callback :function(){//统一处理回调函数 var objs = this.callbacks, args = [].slice.call(arguments) for (var i=0,el;el=objs[i++];) { el.apply(null,args) } this.callbacks = []; }, addCallback : function(obj){ this.callbacks.push(obj) } });
例子中我们做了各种测试,都能过了,不过除IE外,其他浏览器的执行顺序都朋点絮乱了。这缘于标准浏览器并没有对setTimeout的待执行函数弄成一个队列,看来我们要自行搞一个延迟队列才行了……