总体说明
做前端当然少不了ajax的使用,使用dojo的童鞋都知道dojo是基于模块化管理的前端框架,其中对ajax的处理位于dojo/request模块。一般情况下我们使用ajax请求只需要引入dojo/request模块,然后按照文档的说明制定参数即可。实际上dojo在这一模块的处理中抽象了很多概念:
- 平台侦探器:dojo/request/default
- 请求分发器:dojo/request/registry
- 全局通知器:dojo/request/notify
- 数据传输器:dojo/request/xhr dojo/request/script dojo/request/iframe dojo/request/node
- 数据转化器:dojo/request/handlers
处理器的总体关系如下图所示:
正是这些概念使得dojo在ajax请求上能够提供强大的扩展性和简捷的接口。
Provider
请求传输器被称为Provider,dojo框架自身提供了以下4个provider
- dojo/request/xhr 提供跨浏览器的XMLHttpRequest,在浏览器端它被作为默认的provider
- dojo/request/node 用于node平台的异步请求,在node下呗当做默认的provider。dojo是可以运行在node平台下的,当然需要做一些配置,这是另一篇文章的主要内容
- dojo/request/iframe 不刷新浏览器传输form表单,在文件上传时经常用到
- dojo/request/script 常以jsonp方式来进行跨域请求
所有dojo自带的Provider返回一个promise对象,其中有一个不在标准规范内的属性:response。该属性是一个标准promise对象,该对象将一个代表服务器端响应结果的对象作为fulfill状态的值。这个对象有以下几个属性:
关于这几个Provider的详细讲解请继续关注下一篇文章
default
一般情况下我们发送ajax请求时只需引入dojo/request即可,实际上这是在default中根据不同的运行平台自动给我们提供了一个默认的provider。
1 define([ 2 ‘exports‘, 3 ‘require‘, 4 ‘../has‘ 5 ], function(exports, require, has){ 6 //读取dojoConfig中的配置信息 7 var defId = has(‘config-requestProvider‘), 8 platformId; 9 10 //根据不同平台选择不同的provider 11 if(has(‘host-browser‘) || has(‘host-webworker‘)){ 12 platformId = ‘./xhr‘; 13 }else if(has(‘host-node‘)){ 14 platformId = ‘./node‘; 15 /* TODO: 16 }else if(has(‘host-rhino‘)){ 17 platformId = ‘./rhino‘; 18 */ 19 } 20 21 if(!defId){ 22 defId = platformId; 23 } 24 25 exports.getPlatformDefaultId = function(){ 26 return platformId; 27 }; 28 //作为插件使用,是跟参数选择provider 29 exports.load = function(id, parentRequire, loaded, config){ 30 require([id == ‘platform‘ ? platformId : defId], function(provider){ 31 loaded(provider); 32 }); 33 }; 34 });
代码中关于exports跟require模块的说明请看我的上一篇博客:require、module、exports dojo中的三个特殊模块标识。
上述内容关于load的函数的出现,意味着该模块可以作为“插件”使用。dojo插件主要用于加载一些非AMD的资源,比如css、html。dojo中常用的插件有5个:
- dojo/domReady
- dojo/text 用于加载静态资源文件
- dojo/i18n 加载国际化语言文件
- dojo/has 用于特性检测
- dojo/require
当在define或require中一个模块引用包含一个!,dojo的加载器会自动将这个模块引用字符串在!处分开,左边部分作为一个模块引用对待,右边部分,等待左边模块加载完毕后交由模块的load方法处理;
exports.load = function(id, parentRequire, loaded, config){ require([id == ‘platform‘ ? platformId : defId], function(provider){ loaded(provider); }); };
关于load函数的几个参数:
- id:代表!右侧部分
- parentRequire:上下文智能的require请求器
- loaded:id模块加载完毕后的回调
- config:猜测是dojo/_base/config
后三个参数是dojo自己来处理,一般情况下我们不需要关心。
关于插件还要在说几句:
dojo中不会像缓存module一样缓存插件所加载的资源比如:我们可以多次引用同一个module,但是这个module只会加载一次,这是AMD规范所强制规定的。但是我如果多次dojo/text!./template.html这个template.html会被加载多次。
notify
notify是全局的ajax事件通知器,负责全局范围内的ajax事件监听,有类似于jquery中ajaxStart、ajaxComplete的事件。
1 define([‘../Evented‘, ‘../_base/lang‘, ‘./util‘], function(Evented, lang, util){ 2 // module: 3 // dojo/request/notify 4 // summary: 5 // Global notification API for dojo/request. Notifications will 6 // only be emitted if this module is required. 7 // 8 // | require(‘dojo/request‘, ‘dojo/request/notify‘, 9 // | function(request, notify){ 10 // | notify(‘load‘, function(response){ 11 // | if(response.url === ‘someUrl.html‘){ 12 // | console.log(‘Loaded!‘); 13 // | } 14 // | }); 15 // | request.get(‘someUrl.html‘); 16 // | } 17 // | ); 18 19 var pubCount = 0, 20 slice = [].slice; 21 //实例化dojo/Evented对象,负责分发事件 22 var hub = lang.mixin(new Evented, { 23 onsend: function(data){ 24 if(!pubCount){ 25 this.emit(‘start‘); 26 } 27 pubCount++; 28 }, 29 _onload: function(data){ 30 this.emit(‘done‘, data); 31 }, 32 _onerror: function(data){ 33 this.emit(‘done‘, data); 34 }, 35 _ondone: function(data){ 36 if(--pubCount <= 0){ 37 pubCount = 0; 38 this.emit(‘stop‘); 39 } 40 }, 41 emit: function(type, event){ 42 var result = Evented.prototype.emit.apply(this, arguments); 43 44 // After all event handlers have run, run _on* handler 45 //运行完标准事件处理函数后,再来运行本身的私有函数。 46 //load和error事件处理完后触发done事件 47 //done事件处理完毕后,再来运行本身的_ondone函数,然后触发stop事件 48 if(this[‘_on‘ + type]){ 49 this[‘_on‘ + type].apply(this, slice.call(arguments, 1)); 50 } 51 return result; 52 } 53 }); 54 55 function notify(type, listener){ 56 // summary: 57 // Register a listener to be notified when an event 58 // in dojo/request happens. 59 // type: String? 60 // The event to listen for. Events emitted: "start", "send", 61 // "load", "error", "done", "stop". 62 // listener: Function? 63 // A callback to be run when an event happens. 64 // returns: 65 // A signal object that can be used to cancel the listener. 66 // If remove() is called on this signal object, it will 67 // stop the listener from being executed. 68 return hub.on(type, listener); 69 } 70 notify.emit = function(type, event, cancel){ 71 return hub.emit(type, event, cancel); 72 }; 73 74 // Attach notify to dojo/request/util to avoid 75 // try{ require(‘./notify‘); }catch(e){} 76 return util.notify = notify; 77 });
最后的一句:util.notify= notify; util将notify与provider关联起来。
registry
该模块可以在不同的情况下使用不同的provider;匹配的条件可以是正则表达式、字符串或者函数。通过registry可以根据不同的条件注册不同的provider。
1 require(["dojo/request/registry", "dojo/Deferred"], function(request, Deferred){ 2 request.register("crossdomain/ie", xdrProvider); 3 4 var xdrProvider = function(url, options){ 5 var def = new Deferred(); 6 xdr = new XDomainRequest(); 7 if (xdr) { 8 xdr.onerror = function(){ 9 def.reject(‘error‘); 10 }; 11 xdr.ontimeout = function(){ 12 def.reject(‘timeout‘); 13 }; 14 xdr.onprogress = function(){ 15 def.progress(‘progress‘); 16 }; 17 xdr.onload = function(res){ 18 def.resolve(res); 19 }; 20 xdr.timeout = 6000; 21 xdr.open(options.method, url); 22 xdr.send(serilize(options.data)); 23 } else { 24 def.reject("Failed to create"); 25 } 26 27 return def; 28 } 29 30 request.get("crossdomain/ie/getData", { 31 method: "get", 32 data:{id:‘ie9‘} 33 }).then(function(text){ 34 // Do something with the response 35 }); 36 37 });
以下便是registry的源码:
define([ ‘require‘, ‘../_base/array‘, ‘./default!platform‘,//想想notify中的load函数 ‘./util‘ ], function(require, array, fallbackProvider, util){ var providers = []; function request(url, options){ var matchers = providers.slice(0),//作用类似clone i = 0, matcher; while(matcher=matchers[i++]){ if(matcher(url, options)){//匹配provider return matcher.request.call(null, url, options); } } //fallbackProvider由default根据不同平台注入默认的provider return fallbackProvider.apply(null, arguments); } function createMatcher(match, provider){ var matcher; if(provider){ if(match.test){ // RegExp matcher = function(url){ return match.test(url); }; }else if(match.apply && match.call){ matcher = function(){ return match.apply(null, arguments); }; }else{ matcher = function(url){ return url === match; }; } matcher.request = provider; }else{ // If only one argument was passed, assume it is a provider function // to apply unconditionally to all URLs matcher = function(){ return true; }; matcher.request = match; } return matcher; } request.register = function(url, provider, first){ var matcher = createMatcher(url, provider); providers[(first ? ‘unshift‘ : ‘push‘)](matcher); return { remove: function(){ var idx; if(~(idx = array.indexOf(providers, matcher))){ providers.splice(idx, 1); } } }; }; //这里意味着registry也可以使用插件的写法,作用是替换一个默认的provider request.load = function(id, parentRequire, loaded, config){ if(id){ // if there‘s an id, load and set the fallback provider require([id], function(fallback){ fallbackProvider = fallback;//js中的词法作用域,load中永远能够访问到fallbackProvider变量。 loaded(request); }); }else{ loaded(request); } }; util.addCommonMethods(request); return request; });
handlers
XMLHttpRequest对象请求成功后返回的数据格式只有text跟xml两种,handlers根据request中指定的handleAs参数将请求成功后的数据转化为指定类型。与jquery中的类型转化器作用类似。
dojo中提供了以下三种数据转化器:
此外,handlers有跟registry类似的register方法,可以让我们自定义数据转化器。
1 require(["dojo/request/handlers", "dojo/request", "dojo/dom", "dojo/dom-construct", "dojo/json", 2 "dojo/on", "dojo/domReady!"], 3 function(handlers, request, dom, domConst, JSON, on){ 4 handlers.register("custom", function(response){ 5 var data = JSON.parse(response.text); 6 data.hello += "!"; 7 return data; 8 }); 9 10 on(dom.byId("startButton"), "click", function(){ 11 domConst.place("<p>Requesting...</p>", "output"); 12 request("./helloworld.json", { 13 handleAs: "custom" 14 }).then(function(data){ 15 domConst.place("<p>data: <code>" + JSON.stringify(data) + "</code>", "output"); 16 }); 17 }); 18 });
如果您看完本篇文章感觉不错,请点击一下下方的推荐来支持一下博主,谢谢!