跨域的异步请求三

这部分我们继续增强我们的系统,如强制定的回调函数。参考一些类库,这些回调函数暂定如下几种:

  • 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的待执行函数弄成一个队列,看来我们要自行搞一个延迟队列才行了……

var JSONP = function JSONP(url, options) {
	this.options = {callbackParamName: 'callback'};
	for (var prop in options) this.options[prop] = options[prop];
	this.options= options;
	this.url = url;
	this.head = document.getElementsByTagName('head')[0];

	var _this = this;
	this.request();
	if (options.timeout) this.timer = setTimeout(function() {_this.timeoutHandler();}, options.timeout);
}

JSONP.sequence = 0;
JSONP.prototype = {
	constructor: JSONP,

	request: function() {
		var url;
		var script = this.script = document.createElement('script');
		var options = this.options;
		var _this = this;
		this.currentCallbackName = "callback_" + JSONP.sequence++;

		var parameters = this.options.callbackParamName + '=JSONP.' + this.currentCallbackName + '&' + this.composeParams();

		if (this.url.indexOf('?') > -1) url = this.url + '&' + parameters;
		else url = this.url + '?' + parameters;

		JSONP[this.currentCallbackName] = function(json) {
			clearTimeout(_this.timer);
			_this.head.removeChild(script);
			delete JSONP[_this.currentCallbackName];
			if (options.onSuccess) options.onSuccess.call(_this, json);
		};
		script.setAttribute('src', url);
		script.setAttribute('type', 'text/javascript');
		this.head.appendChild(script);
	},

	timeoutHandler: function() {
		if (JSONP[this.currentCallbackName]) {
			JSONP[this.currentCallbackName] = this.nop;
			this.head.removeChild(this.script);
		}
		this.options.onFailure();
	},

	nop: function() {
	},

	composeParams: function() {
		var ret = '';
		var params = this.options.params;
		var tmpArray = [];
		for (var key in params) {
			tmpArray.push(encodeURIComponent(key) + "=" + encodeURIComponent(params[key]));
		}
		return tmpArray.join('&');
	}
}
原文地址:https://www.cnblogs.com/rubylouvre/p/1744685.html