dojo Provider(script、xhr、iframe)源码解析

  总体结构

  dojo/request/script、dojo/request/xhr、dojo/request/iframe这三者是dojo提供的provider。dojo将内部的所有provider构建在Deferred基础上形成异步链式模型,utils.deferred函数向3个provider提供统一接口来规范其行为。数据请求在各个provider的发送过程几乎一致:

  1. 解析options参数util.parseArgs
  2. 创建dfd对象,该对象控制着整个数据接收、处理、传递的过程

    //Make the Deferred object for this xhr request.
            var dfd = util.deferred(
                response,
                cancel,
                isValid,
                isReady,
                handleResponse,
                last
            );
  3. 创建处理last函数(script没有该过程)
  4. 发送请求
  5. watch

  parseArgs函数主要处理三个参数:data(POST方法有效)、query(GET方法有效)、preventCache(添加时间戳防止缓存)

 1 exports.parseArgs = function parseArgs(url, options, skipData){
 2         var data = options.data,
 3             query = options.query;
 4
 5         if(data && !skipData){
 6             if(typeof data === ‘object‘){
 7                 options.data = ioQuery.objectToQuery(data);
 8             }
 9         }
10
11         if(query){
12             if(typeof query === ‘object‘){
13                 query = ioQuery.objectToQuery(query);
14             }
15             if(options.preventCache){
16                 query += (query ? ‘&‘ : ‘‘) + ‘request.preventCache=‘ + (+(new Date));
17             }
18         }else if(options.preventCache){
19             query = ‘request.preventCache=‘ + (+(new Date));
20         }
21
22         if(url && query){
23             url += (~url.indexOf(‘?‘) ? ‘&‘ : ‘?‘) + query;
24         }
25
26         return {
27             url: url,
28             options: options,
29             getHeader: function(headerName){ return null; }
30         };
31     };

  返回的response,是一个代表服务器端返回结果的对象,在这里它还只是一个半成品,需要handleResponse函数中为其装填数据。

  utils.deferred使用为各provider提供统一的接口,来规范数据处理流程,在各provider中需要提供以下参数:

  • 上文中生成的response对象
  • cancel:数据请求被取消之后,provider做自己的逻辑处理
  • isValid根据某些属性判断是否要继续留在_inFlight队列里面(是否还需要进行timeout检查),通常调用handleResponse结束后,isValid为false
  • isReady:根据某些属性判断请求是否成功,成功后调用handleResponse
  • handleResponse:对数据传输的成功与否做不同逻辑处理,由两种方式触发:provider内部根据某些事件触发(如XMLHttpRequest的load事件),watch模块中不断tick检查,isReady为true时触发;请求成功后provider有自己的逻辑处理,通过handlers数据转换器为response装填data和text(有的话),有的provider不需要handlers比如script
  • last作为dfd的第二波链式回调处理,主要作用是在本次请求结束之后的其他逻辑处理

  utils.deferred函数中做了以下三件事:

  1. 创建deferred对象
  2. 为dfd对象装填isValid、isReady、handleResponse方法
  3. 规范数据处理流程

 1 exports.deferred = function deferred(response, cancel, isValid, isReady, handleResponse, last){
 2         var def = new Deferred(function(reason){
 3             cancel && cancel(def, response);
 4
 5             if(!reason || !(reason instanceof RequestError) && !(reason instanceof CancelError)){
 6                 return new CancelError(‘Request canceled‘, response);
 7             }
 8             return reason;
 9         });
10
11         def.response = response;
12         def.isValid = isValid;
13         def.isReady = isReady;
14         def.handleResponse = handleResponse;
15
16         function errHandler(error){
17             error.response = response;
18             throw error;
19         }
20         var responsePromise = def.then(okHandler).otherwise(errHandler);
21
22         if(exports.notify){
23             responsePromise.then(
24                 lang.hitch(exports.notify, ‘emit‘, ‘load‘),
25                 lang.hitch(exports.notify, ‘emit‘, ‘error‘)
26             );
27         }
28
29         var dataPromise = responsePromise.then(dataHandler);
30
31         // http://bugs.dojotoolkit.org/ticket/16794
32         // The following works around a leak in IE9 through the
33         // prototype using lang.delegate on dataPromise and
34         // assigning the result a property with a reference to
35         // responsePromise.
36         var promise = new Promise();
37         for (var prop in dataPromise) {
38             if (dataPromise.hasOwnProperty(prop)) {
39                 promise[prop] = dataPromise[prop];
40             }
41         }
42         promise.response = responsePromise;
43         freeze(promise);
44         // End leak fix
45
46
47         if(last){
48             def.then(function(response){
49                 last.call(def, response);
50             }, function(error){
51                 last.call(def, response, error);
52             });
53         }
54
55         def.promise = promise;
56         def.then = promise.then;//利用闭包(waiting数组在deferred模块中是一个全局变量,)
57
58         return def;
59     };

  请求成功后整个数据处理流程如下:

  watch模块通过不断tick方式来监控请求队列,离开队列的方式有四种:

  1. provider自己触发handleResponse后dfd.isValid为false,移出监控队列
  2. dfd.isReady为true后触发handleResponse,移出监控队列
  3. timeout超时,调用dfd.cancel取消请求,移出队列
  4. window unload事件中取消所有请求,清空队列

 1 var _inFlightIntvl = null,
 2         _inFlight = [];
 3
 4     function watchInFlight(){
 5         // summary:
 6         //        internal method that checks each inflight XMLHttpRequest to see
 7         //        if it has completed or if the timeout situation applies.
 8
 9         var now = +(new Date);
10         // we need manual loop because we often modify _inFlight (and therefore ‘i‘) while iterating
11         for(var i = 0, dfd; i < _inFlight.length && (dfd = _inFlight[i]); i++){
12             var response = dfd.response,
13                 options = response.options;
14             if((dfd.isCanceled && dfd.isCanceled()) || (dfd.isValid && !dfd.isValid(response))){
15                 _inFlight.splice(i--, 1);
16                 watch._onAction && watch._onAction();
17             }else if(dfd.isReady && dfd.isReady(response)){
18                 _inFlight.splice(i--, 1);
19                 dfd.handleResponse(response);
20                 watch._onAction && watch._onAction();
21             }else if(dfd.startTime){
22                 // did we timeout?
23                 if(dfd.startTime + (options.timeout || 0) < now){
24                     _inFlight.splice(i--, 1);
25                     // Cancel the request so the io module can do appropriate cleanup.
26                     dfd.cancel(new RequestTimeoutError(‘Timeout exceeded‘, response));
27                     watch._onAction && watch._onAction();
28                 }
29             }
30         }
31         watch._onInFlight && watch._onInFlight(dfd);
32
33         if(!_inFlight.length){
34             clearInterval(_inFlightIntvl);
35             _inFlightIntvl = null;
36         }
37     }
38
39     function watch(dfd){
40         // summary:
41         //        Watches the io request represented by dfd to see if it completes.
42         // dfd: Deferred
43         //        The Deferred object to watch.
44         // response: Object
45         //        The object used as the value of the request promise.
46         // validCheck: Function
47         //        Function used to check if the IO request is still valid. Gets the dfd
48         //        object as its only argument.
49         // ioCheck: Function
50         //        Function used to check if basic IO call worked. Gets the dfd
51         //        object as its only argument.
52         // resHandle: Function
53         //        Function used to process response. Gets the dfd
54         //        object as its only argument.
55         if(dfd.response.options.timeout){
56             dfd.startTime = +(new Date);
57         }
58
59         if(dfd.isFulfilled()){
60             // bail out if the deferred is already fulfilled
61             return;
62         }
63
64         _inFlight.push(dfd);
65         if(!_inFlightIntvl){
66             _inFlightIntvl = setInterval(watchInFlight, 50);
67         }
68
69         // handle sync requests separately from async:
70         // http://bugs.dojotoolkit.org/ticket/8467
71         if(dfd.response.options.sync){
72             watchInFlight();
73         }
74     }
75
76     watch.cancelAll = function cancelAll(){
77         // summary:
78         //        Cancels all pending IO requests, regardless of IO type
79         try{
80             array.forEach(_inFlight, function(dfd){
81                 try{
82                     dfd.cancel(new CancelError(‘All requests canceled.‘));
83                 }catch(e){}
84             });
85         }catch(e){}
86     };
87
88     if(win && on && win.doc.attachEvent){
89         // Automatically call cancel all io calls on unload in IE
90         // http://bugs.dojotoolkit.org/ticket/2357
91         on(win.global, ‘unload‘, function(){
92             watch.cancelAll();
93         });
94     }

  dojo/request/script

  通过script模块通过动态添加script标签的方式发送请求,该模块支持两种方式来获取数据

  • 设置jsonp参数,以jsonp形式来获取服务器端数据
  • 设置checkString参数,将后台返回的数据挂载到一个全局对象中,通过不断的tick方式检查全局对象是否赋值来进入fulfill回调
  • 如果两个参数都没设置,该script模块会认为仅仅是引入一端外部脚本

  不管使用哪种方式都是以get方式来大宋数据,同时后台必须返回原生的js对象,所以不需要设置handleAs参数。以下是script处理、发送请求的源码:

 1 function script(url, options, returnDeferred){
 2         //解析参数,生成半成品response
 3         var response = util.parseArgs(url, util.deepCopy({}, options));
 4         url = response.url;
 5         options = response.options;
 6
 7         var dfd = util.deferred(//构建dfd对象
 8             response,
 9             canceler,
10             isValid,
11             //这里分为三种情况:jsonp方式无需isReady函数;
12             //checkString方式需要不断检查checkString制定的全局变量;
13             //js脚本方式需要检查script标签是否进入load事件
14             options.jsonp ? null : (options.checkString ? isReadyCheckString : isReadyScript),
15             handleResponse
16         );
17
18         lang.mixin(dfd, {
19             id: mid + (counter++),
20             canDelete: false
21         });
22
23         if(options.jsonp){//处理callback参数,注意加?还是&;有代理情况尤为注意,proxy?url这种情况的处理
24             var queryParameter = new RegExp(‘[?&]‘ + options.jsonp + ‘=‘);
25             if(!queryParameter.test(url)){
26                 url += (~url.indexOf(‘?‘) ? ‘&‘ : ‘?‘) +
27                     options.jsonp + ‘=‘ +
28                     (options.frameDoc ? ‘parent.‘ : ‘‘) +
29                     mid + ‘_callbacks.‘ + dfd.id;
30             }
31
32             dfd.canDelete = true;
33             callbacks[dfd.id] = function(json){
34                 response.data = json;
35                 dfd.handleResponse(response);
36             };
37         }
38
39         if(util.notify){//ajax全局事件
40             util.notify.emit(‘send‘, response, dfd.promise.cancel);
41         }
42
43         if(!options.canAttach || options.canAttach(dfd)){
44             //创建script元素发送请求
45             var node = script._attach(dfd.id, url, options.frameDoc);
46
47             if(!options.jsonp && !options.checkString){
48                 //script加载完毕后设置scriptLoaded,isReadyScript中使用
49                 var handle = on(node, loadEvent, function(evt){
50                     if(evt.type === ‘load‘ || readyRegExp.test(node.readyState)){
51                         handle.remove();
52                         dfd.scriptLoaded = evt;
53                     }
54                 });
55             }
56         }
57         //watch监控请求队列,抹平timeout处理,只有ie跟xhr2才支持原生timeout属性;def.isValid表示是否在检查范围内;
58         watch(dfd);
59
60         return returnDeferred ? dfd : dfd.promise;
61     }

  得到数据后,script模块会删除刚刚添加的script元素。按照我们上面分析的处理逻辑,last函数用于在请求结束后做其他逻辑处理,所以我认为正确的逻辑是放在last中删除script元素,但是dojo中为了兼容低版本ie浏览器,将删除工作放在了isValid函数中。

 1 function isValid(response){
 2         //Do script cleanup here. We wait for one inflight pass
 3         //to make sure we don‘t get any weird things by trying to remove a script
 4         //tag that is part of the call chain (IE 6 has been known to
 5         //crash in that case).
 6         if(deadScripts && deadScripts.length){
 7             array.forEach(deadScripts, function(_script){
 8                 script._remove(_script.id, _script.frameDoc);
 9                 _script.frameDoc = null;
10             });
11             deadScripts = [];
12         }
13
14         return response.options.jsonp ? !response.data : true;
15     }

  发送处理请求的整个过程如下:

  

  dojo/request/xhr

  整个xhr.js分为以下几个部分:

  1. 特性检测
  2. handleResponse函数
  3. 对于不同的XMLHttpRequest使用不同的isValid、isReady、cancel函数
  4. 创建xhr provider
  5. 根据不同条件使用不同的create函数

  xhr函数的处理过程如下:

 1 function xhr(url, options, returnDeferred){
 2         //解析参数
 3         var isFormData = has(‘native-formdata‘) && options && options.data && options.data instanceof FormData;
 4         var response = util.parseArgs(
 5             url,
 6             util.deepCreate(defaultOptions, options),
 7             isFormData
 8         );
 9         url = response.url;
10         options = response.options;
11
12         var remover,
13             last = function(){
14                 remover && remover();//对于xhr2,在请求结束后移除绑定事件
15             };
16
17         //Make the Deferred object for this xhr request.
18         var dfd = util.deferred(
19             response,
20             cancel,
21             isValid,
22             isReady,
23             handleResponse,
24             last
25         );
26         var _xhr = response.xhr = xhr._create();//创建请求对象
27
28         if(!_xhr){
29             // If XHR factory somehow returns nothings,
30             // cancel the deferred.
31             dfd.cancel(new RequestError(‘XHR was not created‘));
32             return returnDeferred ? dfd : dfd.promise;
33         }
34
35         response.getHeader = getHeader;
36
37         if(addListeners){//如果是xhr2,绑定xhr的load、progress、error事件
38             remover = addListeners(_xhr, dfd, response);
39         }
40
41         var data = options.data,
42             async = !options.sync,
43             method = options.method;
44
45         try{//发送请求之前处理其他参数:responseType、withCredential、headers
46             // IE6 won‘t let you call apply() on the native function.
47             _xhr.open(method, url, async, options.user || undefined, options.password || undefined);
48             if(options.withCredentials){
49                 _xhr.withCredentials = options.withCredentials;
50             }
51             if(has(‘native-response-type‘) && options.handleAs in nativeResponseTypes) {
52                 _xhr.responseType = nativeResponseTypes[options.handleAs];
53             }
54             var headers = options.headers,
55                 contentType = isFormData ? false : ‘application/x-www-form-urlencoded‘;
56             if(headers){//对于X-Requested-With单独处理
57                 for(var hdr in headers){
58                     if(hdr.toLowerCase() === ‘content-type‘){
59                         contentType = headers[hdr];
60                     }else if(headers[hdr]){
61                         //Only add header if it has a value. This allows for instance, skipping
62                         //insertion of X-Requested-With by specifying empty value.
63                         _xhr.setRequestHeader(hdr, headers[hdr]);
64                     }
65                 }
66             }
67             if(contentType && contentType !== false){
68                 _xhr.setRequestHeader(‘Content-Type‘, contentType);
69             }
70             //浏览器根据这个请求头来判断http请求是否由ajax方式发出,
71             //设置X-Requested-with:null以欺骗浏览器的方式进行跨域请求(很少使用)
72             if(!headers || !(‘X-Requested-With‘ in headers)){
73                 _xhr.setRequestHeader(‘X-Requested-With‘, ‘XMLHttpRequest‘);
74             }
75             if(util.notify){
76                 util.notify.emit(‘send‘, response, dfd.promise.cancel);
77             }
78             _xhr.send(data);
79         }catch(e){
80             dfd.reject(e);
81         }
82
83         watch(dfd);
84         _xhr = null;
85
86         return returnDeferred ? dfd : dfd.promise;
87     }

  X-Requested-With请求头用于在服务器端判断request来自Ajax请求还是传统请求(判不判断是服务器端的事情)传统同步请求没有这个header头,而ajax请求浏览器会加上这个头,可以通过xhr.setRequestHeader(‘X-Requested-With‘, null)来避免浏览器进行preflight请求。

  xhr模块的整个请求流程如下:

  dojo/request/iframe

  用于xhr无法完成的复杂的请求/响应,体现于两方面:

  • 跨域发送数据(仅仅是发送)
  • 无刷新上传文件

  如果返回的数据不是html或xml格式,比如text、json,必须将数据放在textarea标签中,这是唯一一种可以兼容各个浏览器的获取返回数据的方式。

  

  至于为什么要放到textarea标签中,textarea适合大块文本的输入,textbox只适合单行内容输入,而如果直接将数据以文本形式放到html页面中,某些特殊字符会被转义。注意后台返回的content-type必须是text/html。

  关于iframe上传文件的原理请看我的这篇博客:Javascript无刷新上传文件

  使用iframe发送的所有请求都会被装填到一个队列中,这些请求并不是并行发送而是依次发送,因为该模块只会创建一个iframe。理解了这一点是看懂整个iframe模块代码的关键。

  iframe函数的源码,与上两个provider类似

 1 function iframe(url, options, returnDeferred){
 2         var response = util.parseArgs(url, util.deepCreate(defaultOptions, options), true);
 3         url = response.url;
 4         options = response.options;
 5
 6         if(options.method !== ‘GET‘ && options.method !== ‘POST‘){
 7             throw new Error(options.method + ‘ not supported by dojo/request/iframe‘);
 8         }
 9
10         if(!iframe._frame){
11             iframe._frame = iframe.create(iframe._iframeName, onload + ‘();‘);
12         }
13
14         var dfd = util.deferred(response, null, isValid, isReady, handleResponse, last);
15
16         //_callNext有last函数控制,其中调用_fireNextRequest构成了整个dfdQueue队列调用
17         dfd._callNext = function(){
18             if(!this._calledNext){
19                 this._calledNext = true;
20                 iframe._currentDfd = null;
21                 iframe._fireNextRequest();
22             }
23         };
24         dfd._legacy = returnDeferred;
25
26         iframe._dfdQueue.push(dfd);
27         iframe._fireNextRequest();
28
29         watch(dfd);
30
31         return returnDeferred ? dfd : dfd.promise;
32     }

  主要看一下iframe模块的请求、处理流程:

  

  

  dojo的源码中有大部分处理兼容性的内容,在本篇博客中并未做详细探讨。看源码主要看整体的处理流程和设计思想,兼容性靠的是基础的积累。同时通过翻看dojo源码我也发现自己的薄弱环节,对于dojo源码的解析暂时告一段落,回去恶补基础。。。

时间: 2024-08-12 19:06:46

dojo Provider(script、xhr、iframe)源码解析的相关文章

【转】Java HashMap 源码解析(好文章)

- .fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wrapper iframe, .fluid-width-video-wrapper object, .fluid-width-video-wrapper embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } [

AngularJS源码解析4:Parse解析器的详解

$ParseProvider简介 此服务提供者也是angularjs中用的比较多的,下面我们来详细的说下这个provider. function $ParseProvider() { var cache = {}; var $parseOptions = { csp: false, unwrapPromises: false, logPromiseWarnings: true }; this.unwrapPromises = function(value) { if (isDefined(val

Spring源码解析和配置文件加载

Spring类的继承结构图: Spring运用了大量的模板方法模式和策略模式,所以各位看源码的时候,务必留意,每一个继承的层次都有不同的作用,然后将相同的地方抽取出来,依赖抽象将不同的处理按照不同的策略去处理. 步骤A. 读取 Resource 文件形成 Document 模型  类图: XmlBeanFactory -> XmlBeanDefinitionReader Spring 使用 XmlBeanDefinitionReader 来读取并解析 xml 文件,XmlBeanDefiniti

SpringSecurity 依据用户请求的过程进行源码解析

SpringSecurity实现安全管理主要通过滤器(filter).验证器(AuthenticationManager).用户数据提供器(ProviderManager).授权器(accessDecisionManager).投票器(AccessDecisionVoter)这几个基本模块协作完成的.大概分为两个部分 用户验证 和授权 这个两个部分.这个部分主要在AuthenticationProcessingFilter和AbstractSecurityInterceptor中完成. 使用过S

jQuery方法源码解析--jQuery($)方法(一)

jQuery方法源码解析--jQuery($)方法 注: 1.本文分析的代码为jQuery.1.11.1版本,在官网上下载未压缩版即可 2.转载请注明出处 jQuery方法: 这个方法大家都不陌生,在使用过程中,它还有另外一个名字,美元符号:$,$(...)其实就是jQuery(...); 它有很多种用法,通常都返回一个jquery对象,也可以作为$(document).ready(...);的简写形式,分析之前先看一下jQuery都有什么用法. 1.jQuery( selector [, co

jQuery源码解析(架构与依赖模块)第一章 理解架构

1-1 jQuery设计理念 引用百科的介绍: jQuery是继prototype之后又一个优秀的Javascript框架.它是轻量级的js库 ,它兼容CSS3,还兼容各种浏览器(IE 6.0+, FF 1.5+, Safari 2.0+, Opera 9.0+),jQuery2.0及后续版本将不再支持IE6/7/8浏览器.jQuery使用户能更方便地处理HTML(标准通用标记语言下的一个应用).events.实现动画效果,并且方便地为网站提供AJAX交互.jQuery还有一个比较大的优势是,它

OpenStack Neutron LoadBalance源码解析(一)

声明: 本博客欢迎转载,但请保留原作者信息,并请注明出处! 作者:林凯 团队:华为杭州OpenStack团队 在OpenStackGrizzly版本中,Neutron(当时叫Quantum)组件引入了一个新的网络服务:LoadBalance(LBaaS),关于LoadBalance的框架和基础知识方面网上已经有了一些好文章,在此不再赘述.本文将对LoadBalancer的代码流程及实现进行初步解析,肯定会有错误和不严谨的地方,需要大家给予指正. 推荐一些基础知识的文章给大家,大家看完之后再看源码

Spring Security 解析(七) —— Spring Security Oauth2 源码解析

Spring Security 解析(七) -- Spring Security Oauth2 源码解析 ??在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把Spring Security .Spring Security Oauth2 等权限.认证相关的内容.原理及设计学习并整理一遍.本系列文章就是在学习的过程中加强印象和理解所撰写的,如有侵权请告知. 项目环境: JDK1.8 Spring boot 2.x Spring Security

vue系列---响应式原理实现及Observer源码解析(一)

_ 阅读目录 一. 什么是响应式? 二:如何侦测数据的变化? 2.1 Object.defineProperty() 侦测对象属性值变化 2.2 如何侦测数组的索引值的变化 2.3 如何监听数组内容的增加或减少? 2.4 使用Proxy来实现数据监听 三. Observer源码解析 回到顶部 一. 什么是响应式? 我们可以这样理解,当一个数据状态发生改变的时候,那么与这个数据状态相关的事务也会发生改变.用我们的前端专业术语来讲,当我们JS中的对象数据发生改变的时候,与JS中对象数据相关联的DOM