总体结构

  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(添加时间戳防止缓存)

 exports.parseArgs = function parseArgs(url, options, skipData){
var data = options.data,
query = options.query; if(data && !skipData){
if(typeof data === 'object'){
options.data = ioQuery.objectToQuery(data);
}
} if(query){
if(typeof query === 'object'){
query = ioQuery.objectToQuery(query);
}
if(options.preventCache){
query += (query ? '&' : '') + 'request.preventCache=' + (+(new Date));
}
}else if(options.preventCache){
query = 'request.preventCache=' + (+(new Date));
} if(url && query){
url += (~url.indexOf('?') ? '&' : '?') + query;
} return {
url: url,
options: options,
getHeader: function(headerName){ return null; }
};
};

  返回的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. 规范数据处理流程
 exports.deferred = function deferred(response, cancel, isValid, isReady, handleResponse, last){
var def = new Deferred(function(reason){
cancel && cancel(def, response); if(!reason || !(reason instanceof RequestError) && !(reason instanceof CancelError)){
return new CancelError('Request canceled', response);
}
return reason;
}); def.response = response;
def.isValid = isValid;
def.isReady = isReady;
def.handleResponse = handleResponse; function errHandler(error){
error.response = response;
throw error;
}
var responsePromise = def.then(okHandler).otherwise(errHandler); if(exports.notify){
responsePromise.then(
lang.hitch(exports.notify, 'emit', 'load'),
lang.hitch(exports.notify, 'emit', 'error')
);
} var dataPromise = responsePromise.then(dataHandler); // http://bugs.dojotoolkit.org/ticket/16794
// The following works around a leak in IE9 through the
// prototype using lang.delegate on dataPromise and
// assigning the result a property with a reference to
// responsePromise.
var promise = new Promise();
for (var prop in dataPromise) {
if (dataPromise.hasOwnProperty(prop)) {
promise[prop] = dataPromise[prop];
}
}
promise.response = responsePromise;
freeze(promise);
// End leak fix if(last){
def.then(function(response){
last.call(def, response);
}, function(error){
last.call(def, response, error);
});
} def.promise = promise;
def.then = promise.then;//利用闭包(waiting数组在deferred模块中是一个全局变量,) return def;
};

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

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

  1. provider自己触发handleResponse后dfd.isValid为false,移出监控队列
  2. dfd.isReady为true后触发handleResponse,移出监控队列
  3. timeout超时,调用dfd.cancel取消请求,移出队列
  4. window unload事件中取消所有请求,清空队列
 var _inFlightIntvl = null,
_inFlight = []; function watchInFlight(){
// summary:
// internal method that checks each inflight XMLHttpRequest to see
// if it has completed or if the timeout situation applies. var now = +(new Date);
// we need manual loop because we often modify _inFlight (and therefore 'i') while iterating
for(var i = 0, dfd; i < _inFlight.length && (dfd = _inFlight[i]); i++){
var response = dfd.response,
options = response.options;
if((dfd.isCanceled && dfd.isCanceled()) || (dfd.isValid && !dfd.isValid(response))){
_inFlight.splice(i--, 1);
watch._onAction && watch._onAction();
}else if(dfd.isReady && dfd.isReady(response)){
_inFlight.splice(i--, 1);
dfd.handleResponse(response);
watch._onAction && watch._onAction();
}else if(dfd.startTime){
// did we timeout?
if(dfd.startTime + (options.timeout || 0) < now){
_inFlight.splice(i--, 1);
// Cancel the request so the io module can do appropriate cleanup.
dfd.cancel(new RequestTimeoutError('Timeout exceeded', response));
watch._onAction && watch._onAction();
}
}
}
watch._onInFlight && watch._onInFlight(dfd); if(!_inFlight.length){
clearInterval(_inFlightIntvl);
_inFlightIntvl = null;
}
} function watch(dfd){
// summary:
// Watches the io request represented by dfd to see if it completes.
// dfd: Deferred
// The Deferred object to watch.
// response: Object
// The object used as the value of the request promise.
// validCheck: Function
// Function used to check if the IO request is still valid. Gets the dfd
// object as its only argument.
// ioCheck: Function
// Function used to check if basic IO call worked. Gets the dfd
// object as its only argument.
// resHandle: Function
// Function used to process response. Gets the dfd
// object as its only argument.
if(dfd.response.options.timeout){
dfd.startTime = +(new Date);
} if(dfd.isFulfilled()){
// bail out if the deferred is already fulfilled
return;
} _inFlight.push(dfd);
if(!_inFlightIntvl){
_inFlightIntvl = setInterval(watchInFlight, 50);
} // handle sync requests separately from async:
// http://bugs.dojotoolkit.org/ticket/8467
if(dfd.response.options.sync){
watchInFlight();
}
} watch.cancelAll = function cancelAll(){
// summary:
// Cancels all pending IO requests, regardless of IO type
try{
array.forEach(_inFlight, function(dfd){
try{
dfd.cancel(new CancelError('All requests canceled.'));
}catch(e){}
});
}catch(e){}
}; if(win && on && win.doc.attachEvent){
// Automatically call cancel all io calls on unload in IE
// http://bugs.dojotoolkit.org/ticket/2357
on(win.global, 'unload', function(){
watch.cancelAll();
});
}

  dojo/request/script

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

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

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

 function script(url, options, returnDeferred){
//解析参数,生成半成品response
var response = util.parseArgs(url, util.deepCopy({}, options));
url = response.url;
options = response.options; var dfd = util.deferred(//构建dfd对象
response,
canceler,
isValid,
//这里分为三种情况:jsonp方式无需isReady函数;
//checkString方式需要不断检查checkString制定的全局变量;
//js脚本方式需要检查script标签是否进入load事件
options.jsonp ? null : (options.checkString ? isReadyCheckString : isReadyScript),
handleResponse
); lang.mixin(dfd, {
id: mid + (counter++),
canDelete: false
}); if(options.jsonp){//处理callback参数,注意加?还是&;有代理情况尤为注意,proxy?url这种情况的处理
var queryParameter = new RegExp('[?&]' + options.jsonp + '=');
if(!queryParameter.test(url)){
url += (~url.indexOf('?') ? '&' : '?') +
options.jsonp + '=' +
(options.frameDoc ? 'parent.' : '') +
mid + '_callbacks.' + dfd.id;
} dfd.canDelete = true;
callbacks[dfd.id] = function(json){
response.data = json;
dfd.handleResponse(response);
};
} if(util.notify){//ajax全局事件
util.notify.emit('send', response, dfd.promise.cancel);
} if(!options.canAttach || options.canAttach(dfd)){
//创建script元素发送请求
var node = script._attach(dfd.id, url, options.frameDoc); if(!options.jsonp && !options.checkString){
//script加载完毕后设置scriptLoaded,isReadyScript中使用
var handle = on(node, loadEvent, function(evt){
if(evt.type === 'load' || readyRegExp.test(node.readyState)){
handle.remove();
dfd.scriptLoaded = evt;
}
});
}
}
//watch监控请求队列,抹平timeout处理,只有ie跟xhr2才支持原生timeout属性;def.isValid表示是否在检查范围内;
watch(dfd); return returnDeferred ? dfd : dfd.promise;
}

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

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

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

  

  dojo/request/xhr

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

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

  xhr函数的处理过程如下:

 function xhr(url, options, returnDeferred){
//解析参数
var isFormData = has('native-formdata') && options && options.data && options.data instanceof FormData;
var response = util.parseArgs(
url,
util.deepCreate(defaultOptions, options),
isFormData
);
url = response.url;
options = response.options; var remover,
last = function(){
remover && remover();//对于xhr2,在请求结束后移除绑定事件
}; //Make the Deferred object for this xhr request.
var dfd = util.deferred(
response,
cancel,
isValid,
isReady,
handleResponse,
last
);
var _xhr = response.xhr = xhr._create();//创建请求对象 if(!_xhr){
// If XHR factory somehow returns nothings,
// cancel the deferred.
dfd.cancel(new RequestError('XHR was not created'));
return returnDeferred ? dfd : dfd.promise;
} response.getHeader = getHeader; if(addListeners){//如果是xhr2,绑定xhr的load、progress、error事件
remover = addListeners(_xhr, dfd, response);
} var data = options.data,
async = !options.sync,
method = options.method; try{//发送请求之前处理其他参数:responseType、withCredential、headers
// IE6 won't let you call apply() on the native function.
_xhr.open(method, url, async, options.user || undefined, options.password || undefined);
if(options.withCredentials){
_xhr.withCredentials = options.withCredentials;
}
if(has('native-response-type') && options.handleAs in nativeResponseTypes) {
_xhr.responseType = nativeResponseTypes[options.handleAs];
}
var headers = options.headers,
contentType = isFormData ? false : 'application/x-www-form-urlencoded';
if(headers){//对于X-Requested-With单独处理
for(var hdr in headers){
if(hdr.toLowerCase() === 'content-type'){
contentType = headers[hdr];
}else if(headers[hdr]){
//Only add header if it has a value. This allows for instance, skipping
//insertion of X-Requested-With by specifying empty value.
_xhr.setRequestHeader(hdr, headers[hdr]);
}
}
}
if(contentType && contentType !== false){
_xhr.setRequestHeader('Content-Type', contentType);
}
//浏览器根据这个请求头来判断http请求是否由ajax方式发出,
//设置X-Requested-with:null以欺骗浏览器的方式进行跨域请求(很少使用)
if(!headers || !('X-Requested-With' in headers)){
_xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
}
if(util.notify){
util.notify.emit('send', response, dfd.promise.cancel);
}
_xhr.send(data);
}catch(e){
dfd.reject(e);
} watch(dfd);
_xhr = null; return returnDeferred ? dfd : dfd.promise;
}

  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类似

 function iframe(url, options, returnDeferred){
var response = util.parseArgs(url, util.deepCreate(defaultOptions, options), true);
url = response.url;
options = response.options; if(options.method !== 'GET' && options.method !== 'POST'){
throw new Error(options.method + ' not supported by dojo/request/iframe');
} if(!iframe._frame){
iframe._frame = iframe.create(iframe._iframeName, onload + '();');
} var dfd = util.deferred(response, null, isValid, isReady, handleResponse, last); //_callNext有last函数控制,其中调用_fireNextRequest构成了整个dfdQueue队列调用
dfd._callNext = function(){
if(!this._calledNext){
this._calledNext = true;
iframe._currentDfd = null;
iframe._fireNextRequest();
}
};
dfd._legacy = returnDeferred; iframe._dfdQueue.push(dfd);
iframe._fireNextRequest(); watch(dfd); return returnDeferred ? dfd : dfd.promise;
}

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

  

  

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

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

  1. bitcoin 源码解析 - 交易 Transaction(三) - Script

    bitcoin 源码解析 - 交易 Transaction(三) - Script 之前的章节已经比较粗略的解释了在Transaction体系当中的整体运作原理.接下来的章节会对这个体系进行分解,比较 ...

  2. 【Java实战】源码解析Java SPI(Service Provider Interface )机制原理

    一.背景知识 在阅读开源框架源码时,发现许多框架都支持SPI(Service Provider Interface ),前面有篇文章JDBC对Driver的加载时应用了SPI,参考[Hibernate ...

  3. jQuery2.x源码解析(设计篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...

  4. JQuery源码解析(一)

    写在前面:本<JQuery源码解析>系列是基于一些前辈们的文章进行进一步的分析.细化.修改而写出来的,在这边感谢那些慷慨提供科普文档的技术大拿们. 要查阅JQ的源文件请下载开发版的JQ.j ...

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

    ­ .fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...

  6. OKHttp源码解析

    http://frodoking.github.io/2015/03/12/android-okhttp/ Android为我们提供了两种HTTP交互的方式:HttpURLConnection 和 A ...

  7. jQuery2.x源码解析(DOM操作篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) jQuery这个类库最为核心重要的功能就是DOM ...

  8. Mybatis源码解析-DynamicSqlSource和RawSqlSource的区别

    XMLLanguageDriver是ibatis的默认解析sql节点帮助类,其中的方法其会调用生成DynamicSqlSource和RawSqlSource这两个帮助类,本文将对此作下简单的简析 应用 ...

  9. 【vuejs深入二】vue源码解析之一,基础源码结构和htmlParse解析器

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. vuejs是一个优秀的前端mvvm框架,它的易用性和渐进式的理念可以使每一个前端开发人员感到舒服,感到easy.它内 ...

随机推荐

  1. 用Action的属性接受参数

    版本, @Override是Java5的元数据,自动加上去的一个标志,告诉你说下面这个方法是从父类/接口 继承过来的,需要你重写一次,这样就可以方便你阅读,也不怕会忘记@Override是伪代码,表示 ...

  2. vim 查看文件二进制格式

    用vim打开文件,vim -b file,选项-b是二进制模式打开   然后输入 :%!xxd,就可看到二进制编码     其实在linux下,直接输入xxd file 也是可以看到的文件二进制格式的

  3. [OSI]网络间通信流程

    PC 连接交换机A,组成内网.DNS Serv 和 Web Serv 连接交换机B 组成外网. 示意图: 内网通信 PC1 到 PC2: PC1 发送的数据先到交换机A,交换机A没有ARP地址缓存表, ...

  4. c# Exception 异常信息归整

    private string ErrorMessage(Exception exception) { StringBuilder stringBuilder = new StringBuilder() ...

  5. amr转MP3

    using System; using System.Threading; using System.IO; using System.Diagnostics; using System.Securi ...

  6. 关于几种编码详解(Unicode,UTF-8,GB系列)

    最近学Python,老是被编码的问题搞得晕乎乎的,晚上看了好多篇博客,整理出来一个比较清晰的关于几种编码以及字符集的思路. 主要参考:http://blog.sina.com.cn/s/blog_6d ...

  7. php和syslog

    syslog是Linux系统默认的日志守护进程.使用syslog可以方便把指定的事件写入特定文件中,可以让任何事件都登录到一台或多台服务器上. 1.简单例子,先说一下syslog怎么使用,以php为例 ...

  8. Scatterplots 散点图

    Simple Scatterplot # Simple Scatterplot attach(mtcars)plot(wt, mpg, main="Scatterplot Example&q ...

  9. Qt 为tableview的item添加网格线

    使用qss可以显示每个item的网格: selection-background-color: rgb(170, 170, 127); gridline-color: rgb(255, 255, 25 ...

  10. iOS 清除web cookies

    NSHTTPCookie *cookie; NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; ...