總體說明
做前端當然少不了ajax的使用,使用dojo的童鞋都知道dojo是基於模塊化管理的前端框架,其中對ajax的處理位於dojo/request模塊。一般情況下我們使用ajax請求只需要引入dojo/request模塊,然後按照文檔的說明制定參數即可。實際上dojo在這一模塊的處理中抽象了很多概念:
處理器的總體關係如下圖所示:
正是這些概念使得dojo在ajax請求上能夠提供強大的擴展性和簡捷的接口。
Provider
請求傳輸器被稱爲Provider,dojo框架自身提供了以下4個provider
所有dojo自帶的Provider返回一個promise對象,其中有一個不在標準規範內的屬性:response。該屬性是一個標準promise對象,該對象將一個代表服務器端響應結果的對象作爲fulfill狀態的值。這個對象有以下幾個屬性:
關於這幾個Provider的詳細講解請繼續關注下一篇文章
default
一般情況下我們發送ajax請求時只需引入dojo/request即可,實際上這是在default中根據不同的運行平臺自動給我們提供了一個默認的provider。
define([ 'exports', 'require', '../has' ], function(exports, require, has){ //讀取dojoConfig中的配置信息 var defId = has('config-requestProvider'), platformId; //根據不同平臺選擇不同的provider if(has('host-browser') || has('host-webworker')){ platformId = './xhr'; }else if(has('host-node')){ platformId = './node'; /* TODO: }else if(has('host-rhino')){ platformId = './rhino'; */ } if(!defId){ defId = platformId; } exports.getPlatformDefaultId = function(){ return platformId; }; //作爲插件使用,是跟參數選擇provider exports.load = function(id, parentRequire, loaded, config){ require([id == 'platform' ? platformId : defId], function(provider){ loaded(provider); }); }; });
代碼中關於exports跟require模塊的說明請看我的上一篇博客:require、module、exports dojo中的三個特殊模塊標識。
上述內容關於load的函數的出現,意味着該模塊可以作爲「插件」使用。dojo插件主要用於加載一些非AMD的資源,比如css、html。dojo中常用的插件有5個:
當在define或require中一個模塊引用包含一個!,dojo的加載器會自動將這個模塊引用字符串在!處分開,左邊部分作爲一個模塊引用對待,右邊部分,等待左邊模塊加載完畢後交由模塊的load方法處理;
exports.load = function(id, parentRequire, loaded, config){ require([id == 'platform' ? platformId : defId], function(provider){ loaded(provider); }); };
關於load函數的幾個參數:
後三個參數是dojo自己來處理,一般情況下我們不需要關心。
notify
notify是全局的ajax事件通知器,負責全局範圍內的ajax事件監聽,有類似於jquery中ajaxStart、ajaxComplete的事件。
define(['../Evented', '../_base/lang', './util'], function(Evented, lang, util){ // module: // dojo/request/notify // summary: // Global notification API for dojo/request. Notifications will // only be emitted if this module is required. // // | require('dojo/request', 'dojo/request/notify', // | function(request, notify){ // | notify('load', function(response){ // | if(response.url === 'someUrl.html'){ // | console.log('Loaded!'); // | } // | }); // | request.get('someUrl.html'); // | } // | ); var pubCount = 0, slice = [].slice; //實例化dojo/Evented對象,負責分發事件 var hub = lang.mixin(new Evented, { onsend: function(data){ if(!pubCount){ this.emit('start'); } pubCount++; }, _onload: function(data){ this.emit('done', data); }, _onerror: function(data){ this.emit('done', data); }, _ondone: function(data){ if(--pubCount <= 0){ pubCount = 0; this.emit('stop'); } }, emit: function(type, event){ var result = Evented.prototype.emit.apply(this, arguments); // After all event handlers have run, run _on* handler //運行完標準事件處理函數後,再來運行本身的私有函數。 //load和error事件處理完後觸發done事件 //done事件處理完畢後,再來運行本身的_ondone函數,然後觸發stop事件 if(this['_on' + type]){ this['_on' + type].apply(this, slice.call(arguments, 1)); } return result; } }); function notify(type, listener){ // summary: // Register a listener to be notified when an event // in dojo/request happens. // type: String? // The event to listen for. Events emitted: "start", "send", // "load", "error", "done", "stop". // listener: Function? // A callback to be run when an event happens. // returns: // A signal object that can be used to cancel the listener. // If remove() is called on this signal object, it will // stop the listener from being executed. return hub.on(type, listener); } notify.emit = function(type, event, cancel){ return hub.emit(type, event, cancel); }; // Attach notify to dojo/request/util to avoid // try{ require('./notify'); }catch(e){} return util.notify = notify; });
最後的一句:util.notify= notify; util將notify與provider關聯起來。
registry
該模塊可以在不同的情況下使用不同的provider;匹配的條件可以是正則表達式、字符串或者函數。通過registry可以根據不同的條件註冊不同的provider。
require(["dojo/request/registry", "dojo/Deferred"], function(request, Deferred){ request.register("crossdomain/ie", xdrProvider); var xdrProvider = function(url, options){ var def = new Deferred(); xdr = new XDomainRequest(); if (xdr) { xdr.onerror = function(){ def.reject('error'); }; xdr.ontimeout = function(){ def.reject('timeout'); }; xdr.onprogress = function(){ def.progress('progress'); }; xdr.onload = function(res){ def.resolve(res); }; xdr.timeout = 6000; xdr.open(options.method, url); xdr.send(serilize(options.data)); } else { def.reject("Failed to create"); } return def; } request.get("crossdomain/ie/getData", { method: "get", data:{id:'ie9'} }).then(function(text){ // Do something with the response }); });
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方法,可以讓我們自定義數據轉化器。
require(["dojo/request/handlers", "dojo/request", "dojo/dom", "dojo/dom-construct", "dojo/json", "dojo/on", "dojo/domReady!"], function(handlers, request, dom, domConst, JSON, on){ handlers.register("custom", function(response){ var data = JSON.parse(response.text); data.hello += "!"; return data; }); on(dom.byId("startButton"), "click", function(){ domConst.place("<p>Requesting...</p>", "output"); request("./helloworld.json", { handleAs: "custom" }).then(function(data){ domConst.place("<p>data: <code>" + JSON.stringify(data) + "</code>", "output"); }); }); });