From a66f4025c81d428d0668465c8c7e54e7dd2f3219 Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Mon, 29 Sep 2014 18:09:17 +1000 Subject: Improve errors reporting for distant objects, fix requestor (special __requestor__ dep), error out when jQuery can't be found, handle object registration form --- scorpion.js | 93 ++++++++++++++++++++++++++++++++++++++------------------- scorpion.min.js | 2 +- 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/scorpion.js b/scorpion.js index 6d7051c..af15dda 100644 --- a/scorpion.js +++ b/scorpion.js @@ -4,7 +4,7 @@ var Scorpion = (function() { var Deferred = (function() { var defer = function(fn, value) {setTimeout(function() {fn(value);});}; - var runFn = function(fn, deferred, action) { + var runFn = function(fn, deferred) { return function(value) { try { var result = fn(value); @@ -121,6 +121,13 @@ var Scorpion = (function() { return deferred.promise; }; + Deferred.collapseErrorObject = function(obj) { + var errors = []; + for (var key in obj.errors) + errors = errors.concat(obj.errors[key]); + throw errors; + }; + return Deferred; })(); @@ -202,12 +209,23 @@ var Scorpion = (function() { }; Scorpion.prototype.register = function(name, value) { - for (var i = 0, l = this.plugins.length; i < l; ++i) { - if (this.plugins[i].register(this, name, value)) - return this; + if (typeof(name) == "function" || name instanceof Function) { + value = name; + name = value.name; + } + if (typeof(name) == "string" || name instanceof String) { + for (var i = 0, l = this.plugins.length; i < l; ++i) { + if (this.plugins[i].register(this, name, value)) + return this; + } + throw this.error("No plugin handled registration of: " + name); + return this; + } else { + for (var key in name) { + this.register(key, name[key]); + } + return this; } - throw this.error("No plugin handled registration of: " + name); - return this; }; Scorpion.prototype.invoke = function(spec) { @@ -221,13 +239,13 @@ var Scorpion = (function() { var depPromise = this.waitForAll(depPromises); var result = depPromise.then(function(results) { return fn.apply(injector, results); - }); + }, Deferred.collapseErrorObject); result.destroy = function() { return Scorpion.waitForAll(depPromises.map(function(promise) { return promise.destroy(); })).then(function() { return true; - }); + }, Deferred.collapseErrorObject); }; return result; }; @@ -300,13 +318,19 @@ var Scorpion = (function() { DOMPlugin.prototype.get = function(injector, name, stack) { var deferred = injector.defer(); - var interval = setInterval(function() { - var obj = $(this.aliases[name] || name); - if (obj.length) { - clearInterval(interval); - deferred.resolve(obj); - } - }.bind(this), 100); + if (typeof($) !== "undefined" && $) { + var intervalFn = function() { + var obj = $(this.aliases[name] || name); + if (obj.length) { + clearInterval(interval); + deferred.resolve(obj); + } + }.bind(this); + var interval = setInterval(intervalFn, 100); + intervalFn(); + } else { + deferred.reject(new Error("jQuery was not found " + stack.join(" <- "))); + } return deferred.promise; }; @@ -325,13 +349,17 @@ var Scorpion = (function() { HTTPPlugin.prototype.get = function(injector, name, stack) { var deferred = injector.defer(); - $.ajax(this.aliases[name] || name).then(function(result) { - deferred.resolve(result); - // deferred.resolve.bind(deferred); - }, deferred.reject.bind(deferred)); - deferred.promise.destroy = function() { - return Scorpion.resolved(true); - }; + if (typeof($) !== "undefined" && $) { + $.ajax(this.aliases[name] || name).then(function(result) { + deferred.resolve(result); + // deferred.resolve.bind(deferred); + }, deferred.reject.bind(deferred)); + deferred.promise.destroy = function() { + return Scorpion.resolved(true); + }; + } else { + deferred.reject(new Error("jQuery was not found " + stack.join(" <- "))); + } return deferred.promise; }; @@ -358,21 +386,22 @@ var Scorpion = (function() { var parsed = injector.parseSpec(spec); var constructor = parsed[0]; var dependencies = parsed[1]; + var readRequestor = false; var depPromises = dependencies.map(function(dep) { - return injector.get(dep, [name].concat(stack)); + if (dep == "__requestor__") { + readRequestor = true; + return injector.resolved(stack[1]); + } else { + return injector.get(dep, stack); + } }); var depPromise = injector.waitForAll(depPromises); var onDestroy = null; - var readRequestor = false; var deferred = injector.defer(); var result = deferred.promise; depPromise.then(function(results) { var wrappedScorpion = Object.create(injector); - wrappedScorpion.requestor = function() { - readRequestor = true; - return stack[1]; - }; try { deferred.resolve(constructor.apply(wrappedScorpion, results)); } catch (e) { @@ -383,7 +412,11 @@ var Scorpion = (function() { onDestroy(); } }, function(e) { - deferred.reject(injector.error("Error constructing " + name)); + try { + Deferred.collapseErrorObject(e); + } catch (ex) { + deferred.reject(ex); + } }); result.references = 1; @@ -400,7 +433,7 @@ var Scorpion = (function() { return promise.destroy(); })).then(function() { return true; - }); + }, Deferred.collapseErrorObject); } else { return injector.resolved(true); } diff --git a/scorpion.min.js b/scorpion.min.js index 5524044..bd06026 100644 --- a/scorpion.min.js +++ b/scorpion.min.js @@ -1 +1 @@ -/*! scorpion 0.1.0 */var Scorpion=function(){var a=function(){var a=function(a,b){setTimeout(function(){a(b)})},b=function(a,b){return function(c){try{var d=a(c);d&&d.then?d.then(function(a){b.resolve(a)},function(a){b.reject(a)}):b.resolve(d)}catch(e){b.reject(e)}}},c=function(){this.resolved=!1,this.rejected=!1,this.onSuccess=[],this.onError=[]};c.prototype.then=function(c,e){c=c||function(a){return a},e=e||function(a){throw a};var f=new d,g=b(c,f),h=b(e,f);return this.resolved?a(g,this.value):this.rejected?a(h,this.value):(null!=this.onSuccess&&this.onSuccess.push(g),null!=this.onError&&this.onError.push(h)),f.promise};var d=function(){this.promise=new c};return d.prototype.resolve=function(a){if(this.promise.resolved)throw new Error("Cannot re-resolve already resolved promise");if(this.promise.rejected)throw new Error("Cannot resolve a rejected promise");this.promise.resolved=!0,this.promise.value=a;var b=this.promise.onSuccess;this.promise.onSuccess=null,this.promise.onError=null,setTimeout(function(){b.forEach(function(b){b(a)})})},d.prototype.reject=function(a){if(this.promise.resolved)throw new Error("Cannot reject an already resolved promise");if(this.promise.rejected)throw new Error("Cannot re-reject a rejected promise");this.promise.rejected=!0,this.promise.value=a;var b=this.promise.onError;this.promise.onSuccess=null,this.promise.onError=null,setTimeout(function(){b.forEach(function(b){b(a)})})},d.waitForAll=function(a){var b=new d,c=0,e=0,f={},g={},h=a.length;a.forEach(function(a,b){a.then(function(a){g[b]=a,c++,i()},function(a){f[b]=a,e++,i()})});var i=function(){if(c==h){for(var a=[],d=0,i=h;i>d;++d)a.push(g[d]);b.resolve(a)}else c+e==h&&b.reject({errors:f,values:g})};return i(),b.promise},d}(),b=function(){var a=/^function[^(]*\(([^)]*)\)/,b=function(b){var c=a.exec(b.toString().replace(/\s+/g,""));if(null==c)throw new Error("Unable to parse fn definition");return c[1]?c[1].split(/,/):[]},c=function(a){var c,d;return"function"==typeof a||a instanceof Function?(d=b(a),c=a):(c=a[a.length-1],d=a.slice(0,a.length-1)),[c,d]},d=function(a){var b=a.split("!");if(0==b.length)throw new Error("Invalid dependency: "+a);return 1==b.length?{prefix:"",name:b[0]}:{prefix:b[0],name:b.slice(1).join("!")}};return{spec:c,dep:d}}(),c=function(a,b){var c=function(a){this.plugins=a};c.rejected=function(a,b){var c=this.defer();return c.reject(this.error(a,b)),c.promise},c.prototype.rejected=c.rejected,c.resolved=function(a){var b=this.defer();return b.resolve(a),b.promise},c.prototype.resolved=c.resolved,c.prototype.get=function(a,b){if(b=[a].concat(b||[]),b&&0!=b.lastIndexOf(a))return this.rejected("Cyclic dependency: "+b.join(" <- "));for(var d=0,e=this.plugins.length;e>d;++d){var f=this.plugins[d].get(this,a,b);if(f)return f.destroy||(f.destroy=function(){return c.resolved(!0)}),f}return this.rejected("Unknown dependency: "+b.join(" <- "))},c.prototype.register=function(a,b){for(var c=0,d=this.plugins.length;d>c;++c)if(this.plugins[c].register(this,a,b))return this;throw this.error("No plugin handled registration of: "+a)},c.prototype.invoke=function(a){var b=this,d=this.parseSpec(a),e=d[0],f=d[1],g=f.map(function(a){return b.get(a,[])}),h=this.waitForAll(g),i=h.then(function(a){return e.apply(b,a)});return i.destroy=function(){return c.waitForAll(g.map(function(a){return a.destroy()})).then(function(){return!0})},i},c.error=function(a,b){return new d(a,b)},c.prototype.error=c.error,c.defer=function(){return new a},c.prototype.defer=c.defer,c.parseSpec=b.spec,c.prototype.parseSpec=b.spec,c.waitForAll=a.waitForAll,c.prototype.waitForAll=a.waitForAll;var d=function(a,b){this.name="InjectorError",this.message=a,this.cause=b};return d.prototype=new Error,d.prototype.constructor=d,d.prototype.toString=function(){return"InjectorError: "+this.message+(this.cause?" [caused by "+this.cause+"]":"")},c}(a,b);return c.prefixPlugin=function(a,b){return{register:function(c,d,e){return 0==d.indexOf(a)?b.register(c,d.substr(a.length),e):!1},get:function(c,d,e){return 0==d.indexOf(a)?b.get(c,d.substr(a.length),e):null}}},c.DOMPlugin=function(){var a=function(){this.aliases={}};return a.prototype.register=function(a,b,c){return this.aliases[b]=c,!0},a.prototype.get=function(a,b){var c=a.defer(),d=setInterval(function(){var a=$(this.aliases[b]||b);a.length&&(clearInterval(d),c.resolve(a))}.bind(this),100);return c.promise},a}(),c.HTTPPlugin=function(){var a=function(){this.aliases={}};return a.prototype.register=function(a,b,c){return this.aliases[b]=c,!0},a.prototype.get=function(a,b){var d=a.defer();return $.ajax(this.aliases[b]||b).then(function(a){d.resolve(a)},d.reject.bind(d)),d.promise.destroy=function(){return c.resolved(!0)},d.promise},a}(),c.ValuePlugin=function(){var a=function(){this.specs={},this.values={}};return a.prototype.register=function(a,b,c){return this.specs[b]=c,!0},a.prototype.get=function(a,b,c){if(b in this.values)return this.values[b].references++,this.values[b];if(b in this.specs){var d=this.specs[b],e=a.parseSpec(d),f=e[0],g=e[1],h=g.map(function(d){return a.get(d,[b].concat(c))}),i=a.waitForAll(h),j=null,k=!1,l=a.defer(),m=l.promise;i.then(function(b){var d=Object.create(a);d.requestor=function(){return k=!0,c[1]};try{l.resolve(f.apply(d,b))}catch(e){l.reject(e)}finally{j=d.onDestroy,m.references<=0&&j&&j()}},function(){l.reject(a.error("Error constructing "+b))}),m.references=1;var n=this.values;return m.destroy=function(){return this.references--,this.references<=0?(j&&j(),delete n[b],m.resolved||m.rejected||l.reject(a.error("Promise destroyed before value completed construction")),a.waitForAll(h.map(function(a){return a.destroy()})).then(function(){return!0})):a.resolved(!0)},k?m:this.values[b]=m}return null},a}(),c}();"undefined"!=typeof module&&(module.exports=Scorpion); \ No newline at end of file +/*! scorpion 0.1.0 */var Scorpion=function(){var a=function(){var a=function(a,b){setTimeout(function(){a(b)})},b=function(a,b){return function(c){try{var d=a(c);d&&d.then?d.then(function(a){b.resolve(a)},function(a){b.reject(a)}):b.resolve(d)}catch(e){b.reject(e)}}},c=function(){this.resolved=!1,this.rejected=!1,this.onSuccess=[],this.onError=[]};c.prototype.then=function(c,e){c=c||function(a){return a},e=e||function(a){throw a};var f=new d,g=b(c,f),h=b(e,f);return this.resolved?a(g,this.value):this.rejected?a(h,this.value):(null!=this.onSuccess&&this.onSuccess.push(g),null!=this.onError&&this.onError.push(h)),f.promise};var d=function(){this.promise=new c};return d.prototype.resolve=function(a){if(this.promise.resolved)throw new Error("Cannot re-resolve already resolved promise");if(this.promise.rejected)throw new Error("Cannot resolve a rejected promise");this.promise.resolved=!0,this.promise.value=a;var b=this.promise.onSuccess;this.promise.onSuccess=null,this.promise.onError=null,setTimeout(function(){b.forEach(function(b){b(a)})})},d.prototype.reject=function(a){if(this.promise.resolved)throw new Error("Cannot reject an already resolved promise");if(this.promise.rejected)throw new Error("Cannot re-reject a rejected promise");this.promise.rejected=!0,this.promise.value=a;var b=this.promise.onError;this.promise.onSuccess=null,this.promise.onError=null,setTimeout(function(){b.forEach(function(b){b(a)})})},d.waitForAll=function(a){var b=new d,c=0,e=0,f={},g={},h=a.length;a.forEach(function(a,b){a.then(function(a){g[b]=a,c++,i()},function(a){f[b]=a,e++,i()})});var i=function(){if(c==h){for(var a=[],d=0,i=h;i>d;++d)a.push(g[d]);b.resolve(a)}else c+e==h&&b.reject({errors:f,values:g})};return i(),b.promise},d.collapseErrorObject=function(a){var b=[];for(var c in a.errors)b=b.concat(a.errors[c]);throw b},d}(),b=function(){var a=/^function[^(]*\(([^)]*)\)/,b=function(b){var c=a.exec(b.toString().replace(/\s+/g,""));if(null==c)throw new Error("Unable to parse fn definition");return c[1]?c[1].split(/,/):[]},c=function(a){var c,d;return"function"==typeof a||a instanceof Function?(d=b(a),c=a):(c=a[a.length-1],d=a.slice(0,a.length-1)),[c,d]},d=function(a){var b=a.split("!");if(0==b.length)throw new Error("Invalid dependency: "+a);return 1==b.length?{prefix:"",name:b[0]}:{prefix:b[0],name:b.slice(1).join("!")}};return{spec:c,dep:d}}(),c=function(a,b){var c=function(a){this.plugins=a};c.rejected=function(a,b){var c=this.defer();return c.reject(this.error(a,b)),c.promise},c.prototype.rejected=c.rejected,c.resolved=function(a){var b=this.defer();return b.resolve(a),b.promise},c.prototype.resolved=c.resolved,c.prototype.get=function(a,b){if(b=[a].concat(b||[]),b&&0!=b.lastIndexOf(a))return this.rejected("Cyclic dependency: "+b.join(" <- "));for(var d=0,e=this.plugins.length;e>d;++d){var f=this.plugins[d].get(this,a,b);if(f)return f.destroy||(f.destroy=function(){return c.resolved(!0)}),f}return this.rejected("Unknown dependency: "+b.join(" <- "))},c.prototype.register=function(a,b){if(("function"==typeof a||a instanceof Function)&&(b=a,a=b.name),"string"==typeof a||a instanceof String){for(var c=0,d=this.plugins.length;d>c;++c)if(this.plugins[c].register(this,a,b))return this;throw this.error("No plugin handled registration of: "+a)}for(var e in a)this.register(e,a[e]);return this},c.prototype.invoke=function(b){var d=this,e=this.parseSpec(b),f=e[0],g=e[1],h=g.map(function(a){return d.get(a,[])}),i=this.waitForAll(h),j=i.then(function(a){return f.apply(d,a)},a.collapseErrorObject);return j.destroy=function(){return c.waitForAll(h.map(function(a){return a.destroy()})).then(function(){return!0},a.collapseErrorObject)},j},c.error=function(a,b){return new d(a,b)},c.prototype.error=c.error,c.defer=function(){return new a},c.prototype.defer=c.defer,c.parseSpec=b.spec,c.prototype.parseSpec=b.spec,c.waitForAll=a.waitForAll,c.prototype.waitForAll=a.waitForAll;var d=function(a,b){this.name="ScorpionError",this.message=a,this.cause=b};return d.prototype=new Error,d.prototype.constructor=d,d.prototype.toString=function(){return"ScorpionError: "+this.message+(this.cause?" [caused by "+this.cause+"]":"")},c}(a,b);return c.prefixPlugin=function(a,b){return{register:function(c,d,e){return 0==d.indexOf(a)?b.register(c,d.substr(a.length),e):!1},get:function(c,d,e){return 0==d.indexOf(a)?b.get(c,d.substr(a.length),e):null}}},c.DOMPlugin=function(){var a=function(){this.aliases={}};return a.prototype.register=function(a,b,c){return this.aliases[b]=c,!0},a.prototype.get=function(a,b,c){var d=a.defer();if("undefined"!=typeof $&&$){var e=function(){var a=$(this.aliases[b]||b);a.length&&(clearInterval(f),d.resolve(a))}.bind(this),f=setInterval(e,100);e()}else d.reject(new Error("jQuery was not found "+c.join(" <- ")));return d.promise},a}(),c.HTTPPlugin=function(){var a=function(){this.aliases={}};return a.prototype.register=function(a,b,c){return this.aliases[b]=c,!0},a.prototype.get=function(a,b,d){var e=a.defer();return"undefined"!=typeof $&&$?($.ajax(this.aliases[b]||b).then(function(a){e.resolve(a)},e.reject.bind(e)),e.promise.destroy=function(){return c.resolved(!0)}):e.reject(new Error("jQuery was not found "+d.join(" <- "))),e.promise},a}(),c.ValuePlugin=function(){var b=function(){this.specs={},this.values={}};return b.prototype.register=function(a,b,c){return this.specs[b]=c,!0},b.prototype.get=function(b,c,d){if(c in this.values)return this.values[c].references++,this.values[c];if(c in this.specs){var e=this.specs[c],f=b.parseSpec(e),g=f[0],h=f[1],i=!1,j=h.map(function(a){return"__requestor__"==a?(i=!0,b.resolved(d[1])):b.get(a,d)}),k=b.waitForAll(j),l=null,m=b.defer(),n=m.promise;k.then(function(a){var c=Object.create(b);try{m.resolve(g.apply(c,a))}catch(d){m.reject(d)}finally{l=c.onDestroy,n.references<=0&&l&&l()}},function(b){try{a.collapseErrorObject(b)}catch(c){m.reject(c)}}),n.references=1;var o=this.values;return n.destroy=function(){return this.references--,this.references<=0?(l&&l(),delete o[c],n.resolved||n.rejected||m.reject(b.error("Promise destroyed before value completed construction")),b.waitForAll(j.map(function(a){return a.destroy()})).then(function(){return!0},a.collapseErrorObject)):b.resolved(!0)},i?n:this.values[c]=n}return null},b}(),c}();"undefined"!=typeof module&&(module.exports=Scorpion); \ No newline at end of file -- cgit v1.2.3