UNPKG

angoose

Version:

Angoose is a Remote Method Invocation module that comes with built-in mongoose/angular support. Now you can call server side module in browser just like you're in the server side!

1,718 lines (1,564 loc) 82.1 kB
/** Angoose Client Core */ /** depdnencies: http, Q */ var AngooseClient = function() {}; function BootstrapAngoose(){ if(typeof(console) == 'undefined'){ console = { log: function(){} } } console.log("Bootstraping angoose-client"); // ** getClass ** // // Get the model class, you may call static method such as findXXX or create new model instance // // var SampleUser = angoose.getClass('SampleUser'); // SampleUser.findOne(); // var u = new SampleUser(); // var counterBase = Math.round(Math.random() * 10000) * 10000; var nextCounter = function() { return counterBase++; } var encode$ = function(obj){ if(!obj || typeof obj != 'object') return obj; if(obj && obj.length){ for(var i=0;i<obj.length;i++){ obj[i] = encode$(obj[i]); }; } else{ //console.log("Type", (typeof obj), obj) for(var key in obj){ var val = encode$(obj[key]); if(key.indexOf('$')==0){ delete obj[key]; key = "_mongo_"+key; } obj[key] = val; } } return obj; } var getter = function (path) { var pieces = path.split('.'); var obj = this; for (var i = 0, l = pieces.length; i < l; i++) { obj = undefined === obj || null === obj ? undefined : obj[pieces[i]]; } return obj; }; var handleReturn = function(retval, callback, deferred, error){ if(callback) return callback(error, retval); return error? deferred.reject(error) : deferred.resolve(retval); } function staticInvoker(modelClass, methodName, methodArgs) { return invoker( modelClass, methodName, methodArgs, null); }; function instanceInvoker(modelClass, model, methodName, methodArgs) { //var modelClass = getClass(model.classname$) return invoker( modelClass, methodName, methodArgs, model); } function invoker(modelClass, methodName, methodArgs, modelInstance) { // modelInstance can be null, everything else is required. var modelName = modelClass.modelName; console.log("invoker: ", modelName, methodName, methodArgs, modelClass); var callback = null; var isStatic = modelInstance==null; var depends = modelClass.dependencies$; var data = { method : methodName, seqnumber : nextCounter(), args : [], clazz: modelName, static: isStatic } for (var i = 0; methodArgs && methodArgs.length > i; i++) { if(typeof methodArgs[i] != 'function') data.args.push(methodArgs[i]); else{ callback = methodArgs[i] data.args.push("$callback") console.log("Callback provided"); } } if(!isStatic) { data.instance = modelInstance; // including model instance data for instance methods } // angular http ignores all key names starting with $, this will break the mongo query data = encode$(data); console.log("****** BEGIN Client Call #", data.seqnumber,modelName, methodName, data); var http = depends['http']; var theQ = depends['promise']; if (!http || !theQ) throw "Missing http and/or Q dependencies"; var deferred = theQ.defer(); var ret = http.post((getConfigs().urlPrefix || '/angoose') + "/rmi/" + modelName + "/" + methodName, data); ret.done(function(retdata) { console.log("****** END Client Call #", data.seqnumber, " result:", retdata); var val = undefined; //@todo construct object, ret value types: 1) model data, 2) list of model data, 3) String, 4) object if (retdata && retdata.retval && ( retdata.datatype =='model' || retdata.datatype =='models')) { if(retdata.datatype == 'model'){ val = new modelClass(retdata.retval); } else{ var models = []; for(var i =0; i< retdata.retval.length; i++){ models.push( new modelClass( retdata.retval[i])); } val = models; } //return handleReturn(val, callback, deferred); // //return deferred.resolve(models); } if (retdata && retdata.instance && !isStatic) { // state has been changed on the server side modelInstance.mergeData(retdata.instance); console.log("Merged server side data"); } if(retdata.success) return handleReturn(val || retdata.retval, callback, deferred); var ex = retdata.exception? (retdata.exception.message || retdata.exception.value) : retdata; return handleReturn(null, callback, deferred, ex); //deferred.reject(ex) }, function(errdata) { console.log("****** END Client Call #", data.seqnumber, " error:", errdata); if(!errdata) return handleReturn(null, callback, deferred, "Unexpected server error occurred, please contact admin"); var ex = errdata.exception? (errdata.exception.message || errdata.exception.value) : errdata; return handleReturn(null, callback, deferred, ex); //return deferred.reject(ex); }) if(!callback) return deferred.promise; } function createProxy(module, funcName, func, funcType){ if(typeof(func) == 'function') return func; if(typeof(func) == "string" && func.indexOf("function") == 0){ var vname; return eval("vname="+func); } if(funcType == 'static'){ return function proxy(){ return staticInvoker(module, funcName, arguments); } }; if(funcType == 'instance'){ return function proxy(){ return instanceInvoker(module, this, funcName, arguments); } }; } /** compile the model based on the server side schema */ function compile(modelName, schema, dependencies) { console.log("Client: compiling schema " , modelName) var model = function AngooseModule(data) { //@todo proper clone for (var i in data) { this[i] = data[i]; } }; // static methods for(var name in schema.statics){ model[name] = createProxy(model, name, schema.statics[name], 'static'); } for(var name in schema.methods){ model.prototype[name] = createProxy(model, name, schema.methods[name], 'instance'); } //model.angoose$ = staticInvoker; model.dependencies$ = dependencies; model.schema = schema; //model.prototype.angoose$ = instanceInvoker; //model.prototype.classname$ = modelName; //model.prototype.schema$ = schema; model.prototype.get = getter; model.modelName = modelName; // this is to be compatible with backend mongoose model.name = modelName; // merge data into this instance model.prototype.mergeData = function(source) { if ( typeof source != "object") throw "Invalid source object, must be an model instance"; //@todo: proper implementation for (var i in source) { this[i] = source[i]; } } AngooseClient.models = AngooseClient.models || {}; AngooseClient.models[modelName] = model; return model; }; function modelNames() { var ret = []; for (var key in getSchemas()) { ret.push(key); } return ret; }; function init(dependencies) { if (AngooseClient.initialized) { console.log("init has been called"); return; } AngooseClient.dependencies = dependencies; console.log("Initializing client angoose models") for (var mName in getSchemas()) { compile(mName, getSchemas()[mName], dependencies); } AngooseClient.initialized = true; }; function getClass(name) { if (!AngooseClient.initialized) throw "Angoose models not initialized yet"; return AngooseClient.models[name]; }; function getConfigs() { return {"urlPrefix":"/angoose"} }; function getSchemas() { return { "SampleService": { "moduleName": "SampleService", "methods": {}, "statics": { "listFavoriteDestinations": "$PROXIED$", "testExecutionContext": "$PROXIED$", "testPromiseReturn": "$PROXIED$", "testErrorBack": "$PROXIED$", "config": "$PROXIED$" } }, "SampleUser": { "moduleName": "SampleUser", "methods": { "getFullname": "function portable(){\n //_instance_portable\n console.log(\"getFullname\", this);\n return (this.firstname ? this.firstname +\" \": \"\") + (this.lastname || \"\");\n}", "setPassword": "$PROXIED$", "save": "$PROXIED$", "remove": "$PROXIED$", "populate": "$PROXIED$" }, "statics": { "getSample": "$PROXIED$", "checkExists": "$PROXIED$", "populate": "$PROXIED$", "find": "$PROXIED$", "findOne": "$PROXIED$", "findById": "$PROXIED$", "findByIdAndRemove": "$PROXIED$", "findByIdAndUpdate": "$PROXIED$", "findOneAndRemove": "$PROXIED$", "findOneAndUpdate": "$PROXIED$", "update": "$PROXIED$", "remove": "$PROXIED$", "count": "$PROXIED$", "geoNear": "$PROXIED$", "geoSearch": "$PROXIED$", "create": "$PROXIED$" }, "paths": { "email": { "enumValues": [], "regExp": null, "path": "email", "instance": "String", "setters": [], "getters": [], "options": { "type": "String", "required": true, "match": {}, "unique": true }, "_index": { "unique": true, "background": true }, "isRequired": true }, "firstname": { "enumValues": [], "regExp": null, "path": "firstname", "instance": "String", "setters": [], "getters": [], "options": { "type": "String", "required": true }, "_index": null, "isRequired": true }, "lastname": { "enumValues": [], "regExp": null, "path": "lastname", "instance": "String", "setters": [], "getters": [], "options": { "type": "String", "required": true }, "_index": null, "isRequired": true }, "status": { "enumValues": [ "inactive", "active", "disabled", "archived" ], "regExp": null, "path": "status", "instance": "String", "setters": [], "getters": [], "options": { "type": "String", "enum": [ "inactive", "active", "disabled", "archived" ], "required": true, "def": "inactive" }, "_index": null, "isRequired": true }, "password": { "enumValues": [], "regExp": null, "path": "password", "instance": "String", "setters": [], "getters": [], "options": { "type": "String" }, "_index": null }, "verified": { "path": "verified", "setters": [], "getters": [], "options": { "type": "Boolean" }, "_index": null }, "groupRef": { "path": "groupRef", "instance": "ObjectID", "setters": [], "getters": [], "options": { "type": "ObjectId", "ref": "SampeUserGroup" }, "_index": null }, "_id": { "path": "_id", "instance": "ObjectID", "setters": [ "not-supported" ], "getters": [], "options": { "type": "ObjectId", "auto": true }, "_index": null, "defaultValue": "not-supported" }, "__v": { "path": "__v", "instance": "Number", "setters": [], "getters": [], "options": { "type": "Number" }, "_index": null } }, "options": { "collection": "SampleUsers", "discriminatorKey": "type", "id": true, "noVirtualId": false, "_id": true, "noId": false, "read": null, "shardKey": null, "autoIndex": true, "minimize": true, "versionKey": "__v", "capped": false, "bufferCommands": true, "strict": true, "pluralization": true } }, "SampleUserGroup": { "moduleName": "SampleUserGroup", "methods": { "save": "$PROXIED$", "remove": "$PROXIED$", "populate": "$PROXIED$" }, "statics": { "populate": "$PROXIED$", "find": "$PROXIED$", "findOne": "$PROXIED$", "findById": "$PROXIED$", "findByIdAndRemove": "$PROXIED$", "findByIdAndUpdate": "$PROXIED$", "findOneAndRemove": "$PROXIED$", "findOneAndUpdate": "$PROXIED$", "update": "$PROXIED$", "remove": "$PROXIED$", "count": "$PROXIED$", "geoNear": "$PROXIED$", "geoSearch": "$PROXIED$", "create": "$PROXIED$" }, "paths": { "name": { "enumValues": [], "regExp": null, "path": "name", "instance": "String", "setters": [], "getters": [], "options": { "type": "String", "required": true, "unique": true }, "_index": { "unique": true, "background": true }, "isRequired": true }, "description": { "enumValues": [], "regExp": null, "path": "description", "instance": "String", "setters": [], "getters": [], "options": { "type": "String" }, "_index": null }, "_id": { "path": "_id", "instance": "ObjectID", "setters": [ "not-supported" ], "getters": [], "options": { "type": "ObjectId", "auto": true }, "_index": null, "defaultValue": "not-supported" }, "__v": { "path": "__v", "instance": "Number", "setters": [], "getters": [], "options": { "type": "Number" }, "_index": null } }, "options": { "collection": "SampleGroups", "id": true, "noVirtualId": false, "_id": true, "noId": false, "read": null, "shardKey": null, "autoIndex": true, "minimize": true, "discriminatorKey": "__t", "versionKey": "__v", "capped": false, "bufferCommands": true, "strict": true, "pluralization": true } }, "Todo": { "moduleName": "Todo", "methods": { "save": "$PROXIED$", "remove": "$PROXIED$", "populate": "$PROXIED$" }, "statics": { "populate": "$PROXIED$", "find": "$PROXIED$", "findOne": "$PROXIED$", "findById": "$PROXIED$", "findByIdAndRemove": "$PROXIED$", "findByIdAndUpdate": "$PROXIED$", "findOneAndRemove": "$PROXIED$", "findOneAndUpdate": "$PROXIED$", "update": "$PROXIED$", "remove": "$PROXIED$", "count": "$PROXIED$", "geoNear": "$PROXIED$", "geoSearch": "$PROXIED$", "create": "$PROXIED$" }, "paths": { "title": { "enumValues": [], "regExp": null, "path": "title", "instance": "String", "setters": [], "getters": [], "options": { "type": "String", "required": true }, "_index": null, "isRequired": true }, "completed": { "path": "completed", "setters": [], "getters": [], "options": { "type": "Boolean" }, "_index": null }, "_id": { "path": "_id", "instance": "ObjectID", "setters": [ "not-supported" ], "getters": [], "options": { "type": "ObjectId", "auto": true }, "_index": null, "defaultValue": "not-supported" }, "__v": { "path": "__v", "instance": "Number", "setters": [], "getters": [], "options": { "type": "Number" }, "_index": null } }, "options": { "id": true, "noVirtualId": false, "_id": true, "noId": false, "read": null, "shardKey": null, "autoIndex": true, "minimize": true, "discriminatorKey": "__t", "versionKey": "__v", "capped": false, "bufferCommands": true, "strict": true, "pluralization": true } } } }; AngooseClient.getClass = getClass; AngooseClient.model = getClass; AngooseClient.modelNames = modelNames; AngooseClient.init = init; AngooseClient.module = getClass; } BootstrapAngoose(); if (typeof(angoose) == 'undefined'){ function angoose(name){ return AngooseClient.getClass(name); } } /****** Angular Adapter for Angoose Client *******/ // Angular client for Angoose automatically register the model and service classes as angular injectables. // For instance, with the `SampleUser` model, you can inject in your controller: // // angular.module('myapp', ['angoose.client']) // .controller('MyCtroller', function($scope, SampleUser){ // $scope.users = SampleUser.$query({'status':'active'}); // }) // (function(){ if(typeof angular =='undefined'){ console.log("No angular") return; } console.log("##### Angoose Client Loading"); var angularModule = angular.module("angoose.client", []); AngooseClient.client='angular'; // ** angoose ** // // You may require `angoose` client as an injectable // angularModule.factory('angoose', function($http, $q){ var myQ = addDoneMethod($q); AngooseClient.init({ http:angularHttpWrapper($http, myQ), promise: myQ }); return AngooseClient; }) function factoryFunc(modelName){ console.log("Create factory for model "+ modelName) angularModule.factory(modelName , function($http, $q, $rootScope, $timeout, angoose){ var acModel = AngooseClient.model(modelName); resourceAdapt(acModel, $rootScope, $timeout); return acModel; }); } angular.forEach( AngooseClient.modelNames(), factoryFunc ); function addDoneMethod($q){ var myQ = angular.extend({}, $q) myQ.defer = function(){ var out = $q.defer.apply($q, arguments); if(out.promise.done) return out; out.promise.done = function(successCallback, errorCallback){ out.promise.then(successCallback, errorCallback); } return out; } return myQ; } function angularHttpWrapper($http, $q){ var ret = {}; ret.post = function(){ var deferred = $q.defer(); $http.post.apply($http, arguments).success(function(data){ deferred.resolve( data ); }).error(function(err){ deferred.reject(err); }); return deferred.promise; } return ret; } function resourceAdapt(modelClass, $rootScope, $timeout){ // now adpat to angular resource modelClass.$get= function(){ if(!modelClass.findOne) throw "Model does not support findOne operation"; var modelInstance = new modelClass(); modelClass.findOne.apply(modelClass, arguments).done(function(object){ // the return data should be a model instance modelInstance.mergeData(object); //$rootScope.$digest(); console.log("Copied server data to placeholder", modelInstance) }, function(err){ console.error("Unable to load server data", err); }); return modelInstance; }; modelClass.$query=function(){ if(!modelClass.find) throw "Model does not support find operation"; var models = []; modelClass.find.apply(modelClass, arguments).done(function(retModels){ // the return data should be a list of models models.length = 0; for(var i=0; retModels && retModels.length>i; i++) models[i] = retModels[i]; //$rootScope.$digest(); console.log("Copied server data to placeholder") }, function(err){ console.error("Unable to load server data", err); }); return models; }; } })(); // testing functions function $anget(serviceName){ return angular.element(document).injector().get(serviceName) }// depdnencies: http, Q /****** jQuery plugin for Angoose Client *******/ (function(){ if(AngooseClient.client) return; if(typeof($) == 'undefined' && typeof(jQuery) == 'undefined') return; AngooseClient.client = 'jquery'; var $ = $ || jQuery; var $q = typeof(Q) == 'undefined'? createQ() : Q; console.log("##### Angoose Client for jQuery Initializing"); AngooseClient.init({ http: ajaxHttpWrapper(), promise: $q }); function ajaxHttpWrapper( ){ var ret = {}; ret.post = function(){ var deferred = $q.defer(); $.post.apply($, arguments).done(function(data, textStatus, jqXHR ){ console.log("Post return ", data) deferred.resolve( data ); }).fail(function( jqXHR, textStatus, err){ deferred.reject(err); }); return deferred.promise; } return ret; } })(); // testing functions function $anget(serviceName){ return angular.element(document).injector().get(serviceName) } /** include Q for now */ function createQ() { "use strict"; var hasStacks = false; try { throw new Error(); } catch (e) { hasStacks = !!e.stack; } // All code after this point will be filtered from stack traces reported // by Q. var qStartingLine = captureLine(); var qFileName; // shims // used for fallback in "allResolved" var noop = function () {}; // Use the fastest possible means to execute a task in a future turn // of the event loop. var nextTick =(function () { // linked list of tasks (single, with head node) var head = {task: void 0, next: null}; var tail = head; var flushing = false; var requestTick = void 0; var isNodeJS = false; function flush() { /* jshint loopfunc: true */ while (head.next) { head = head.next; var task = head.task; head.task = void 0; var domain = head.domain; if (domain) { head.domain = void 0; domain.enter(); } try { task(); } catch (e) { if (isNodeJS) { // In node, uncaught exceptions are considered fatal errors. // Re-throw them synchronously to interrupt flushing! // Ensure continuation if the uncaught exception is suppressed // listening "uncaughtException" events (as domains does). // Continue in next event to avoid tick recursion. if (domain) { domain.exit(); } setTimeout(flush, 0); if (domain) { domain.enter(); } throw e; } else { // In browsers, uncaught exceptions are not fatal. // Re-throw them asynchronously to avoid slow-downs. setTimeout(function() { throw e; }, 0); } } if (domain) { domain.exit(); } } flushing = false; } nextTick = function (task) { tail = tail.next = { task: task, domain: isNodeJS && process.domain, next: null }; if (!flushing) { flushing = true; requestTick(); } }; if (typeof process !== "undefined" && process.nextTick) { // Node.js before 0.9. Note that some fake-Node environments, like the // Mocha test runner, introduce a `process` global without a `nextTick`. isNodeJS = true; requestTick = function () { process.nextTick(flush); }; } else if (typeof setImmediate === "function") { // In IE10, Node.js 0.9+, or https://github.com/NobleJS/setImmediate if (typeof window !== "undefined") { requestTick = setImmediate.bind(window, flush); } else { requestTick = function () { setImmediate(flush); }; } } else if (typeof MessageChannel !== "undefined") { // modern browsers // http://www.nonblocking.io/2011/06/windownexttick.html var channel = new MessageChannel(); // At least Safari Version 6.0.5 (8536.30.1) intermittently cannot create // working message ports the first time a page loads. channel.port1.onmessage = function () { requestTick = requestPortTick; channel.port1.onmessage = flush; flush(); }; var requestPortTick = function () { // Opera requires us to provide a message payload, regardless of // whether we use it. channel.port2.postMessage(0); }; requestTick = function () { setTimeout(flush, 0); requestPortTick(); }; } else { // old browsers requestTick = function () { setTimeout(flush, 0); }; } return nextTick; })(); // Attempt to make generics safe in the face of downstream // modifications. // There is no situation where this is necessary. // If you need a security guarantee, these primordials need to be // deeply frozen anyway, and if you donbt need a security guarantee, // this is just plain paranoid. // However, this **might** have the nice side-effect of reducing the size of // the minified code by reducing x.call() to merely x() // See Mark Millerbs explanation of what this does. // http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming var call = Function.call; function uncurryThis(f) { return function () { return call.apply(f, arguments); }; } // This is equivalent, but slower: // uncurryThis = Function_bind.bind(Function_bind.call); // http://jsperf.com/uncurrythis var array_slice = uncurryThis(Array.prototype.slice); var array_reduce = uncurryThis( Array.prototype.reduce || function (callback, basis) { var index = 0, length = this.length; // concerning the initial value, if one is not provided if (arguments.length === 1) { // seek to the first value in the array, accounting // for the possibility that is is a sparse array do { if (index in this) { basis = this[index++]; break; } if (++index >= length) { throw new TypeError(); } } while (1); } // reduce for (; index < length; index++) { // account for the possibility that the array is sparse if (index in this) { basis = callback(basis, this[index], index); } } return basis; } ); var array_indexOf = uncurryThis( Array.prototype.indexOf || function (value) { // not a very good shim, but good enough for our one use of it for (var i = 0; i < this.length; i++) { if (this[i] === value) { return i; } } return -1; } ); var array_map = uncurryThis( Array.prototype.map || function (callback, thisp) { var self = this; var collect = []; array_reduce(self, function (undefined, value, index) { collect.push(callback.call(thisp, value, index, self)); }, void 0); return collect; } ); var object_create = Object.create || function (prototype) { function Type() { } Type.prototype = prototype; return new Type(); }; var object_hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty); var object_keys = Object.keys || function (object) { var keys = []; for (var key in object) { if (object_hasOwnProperty(object, key)) { keys.push(key); } } return keys; }; var object_toString = uncurryThis(Object.prototype.toString); function isObject(value) { return value === Object(value); } // generator related shims // FIXME: Remove this function once ES6 generators are in SpiderMonkey. function isStopIteration(exception) { return ( object_toString(exception) === "[object StopIteration]" || exception instanceof QReturnValue ); } // FIXME: Remove this helper and Q.return once ES6 generators are in // SpiderMonkey. var QReturnValue; if (typeof ReturnValue !== "undefined") { QReturnValue = ReturnValue; } else { QReturnValue = function (value) { this.value = value; }; } // Until V8 3.19 / Chromium 29 is released, SpiderMonkey is the only // engine that has a deployed base of browsers that support generators. // However, SM's generators use the Python-inspired semantics of // outdated ES6 drafts. We would like to support ES6, but we'd also // like to make it possible to use generators in deployed browsers, so // we also support Python-style generators. At some point we can remove // this block. var hasES6Generators; try { /* jshint evil: true, nonew: false */ new Function("(function* (){ yield 1; })"); hasES6Generators = true; } catch (e) { hasES6Generators = false; } // long stack traces var STACK_JUMP_SEPARATOR = "From previous event:"; function makeStackTraceLong(error, promise) { // If possible, transform the error stack trace by removing Node and Q // cruft, then concatenating with the stack trace of `promise`. See #57. if (hasStacks && promise.stack && typeof error === "object" && error !== null && error.stack && error.stack.indexOf(STACK_JUMP_SEPARATOR) === -1 ) { var stacks = []; for (var p = promise; !!p; p = p.source) { if (p.stack) { stacks.unshift(p.stack); } } stacks.unshift(error.stack); var concatedStacks = stacks.join("\n" + STACK_JUMP_SEPARATOR + "\n"); error.stack = filterStackString(concatedStacks); } } function filterStackString(stackString) { var lines = stackString.split("\n"); var desiredLines = []; for (var i = 0; i < lines.length; ++i) { var line = lines[i]; if (!isInternalFrame(line) && !isNodeFrame(line) && line) { desiredLines.push(line); } } return desiredLines.join("\n"); } function isNodeFrame(stackLine) { return stackLine.indexOf("(module.js:") !== -1 || stackLine.indexOf("(node.js:") !== -1; } function getFileNameAndLineNumber(stackLine) { // Named functions: "at functionName (filename:lineNumber:columnNumber)" // In IE10 function name can have spaces ("Anonymous function") O_o var attempt1 = /at .+ \((.+):(\d+):(?:\d+)\)$/.exec(stackLine); if (attempt1) { return [attempt1[1], Number(attempt1[2])]; } // Anonymous functions: "at filename:lineNumber:columnNumber" var attempt2 = /at ([^ ]+):(\d+):(?:\d+)$/.exec(stackLine); if (attempt2) { return [attempt2[1], Number(attempt2[2])]; } // Firefox style: "function@filename:lineNumber or @filename:lineNumber" var attempt3 = /.*@(.+):(\d+)$/.exec(stackLine); if (attempt3) { return [attempt3[1], Number(attempt3[2])]; } } function isInternalFrame(stackLine) { var fileNameAndLineNumber = getFileNameAndLineNumber(stackLine); if (!fileNameAndLineNumber) { return false; } var fileName = fileNameAndLineNumber[0]; var lineNumber = fileNameAndLineNumber[1]; return fileName === qFileName && lineNumber >= qStartingLine && lineNumber <= qEndingLine; } // discover own file name and line number range for filtering stack // traces function captureLine() { if (!hasStacks) { return; } try { throw new Error(); } catch (e) { var lines = e.stack.split("\n"); var firstLine = lines[0].indexOf("@") > 0 ? lines[1] : lines[2]; var fileNameAndLineNumber = getFileNameAndLineNumber(firstLine); if (!fileNameAndLineNumber) { return; } qFileName = fileNameAndLineNumber[0]; return fileNameAndLineNumber[1]; } } function deprecate(callback, name, alternative) { return function () { if (typeof console !== "undefined" && typeof console.warn === "function") { console.warn(name + " is deprecated, use " + alternative + " instead.", new Error("").stack); } return callback.apply(callback, arguments); }; } // end of shims // beginning of real work /** * Constructs a promise for an immediate reference, passes promises through, or * coerces promises from different systems. * @param value immediate reference or promise */ function Q(value) { // If the object is already a Promise, return it directly. This enables // the resolve function to both be used to created references from objects, // but to tolerably coerce non-promises to promises. if (isPromise(value)) { return value; } // assimilate thenables if (isPromiseAlike(value)) { return coerce(value); } else { return fulfill(value); } } Q.resolve = Q; /** * Performs a task in a future turn of the event loop. * @param {Function} task */ Q.nextTick = nextTick; /** * Controls whether or not long stack traces will be on */ Q.longStackSupport = false; /** * Constructs a {promise, resolve, reject} object. * * `resolve` is a callback to invoke with a more resolved value for the * promise. To fulfill the promise, invoke `resolve` with any value that is * not a thenable. To reject the promise, invoke `resolve` with a rejected * thenable, or invoke `reject` with the reason directly. To resolve the * promise to another thenable, thus putting it in the same state, invoke * `resolve` with that other thenable. */ Q.defer = defer; function defer() { // if "messages" is an "Array", that indicates that the promise has not yet // been resolved. If it is "undefined", it has been resolved. Each // element of the messages array is itself an array of complete arguments to // forward to the resolved promise. We coerce the resolution value to a // promise using the `resolve` function because it handles both fully // non-thenable values and other thenables gracefully. var messages = [], progressListeners = [], resolvedPromise; var deferred = object_create(defer.prototype); var promise = object_create(Promise.prototype); promise.promiseDispatch = function (resolve, op, operands) { var args = array_slice(arguments); if (messages) { messages.push(args); if (op === "when" && operands[1]) { // progress operand progressListeners.push(operands[1]); } } else { nextTick(function () { resolvedPromise.promiseDispatch.apply(resolvedPromise, args); }); } }; // XXX deprecated promise.valueOf = function () { if (messages) { return promise; } var nearerValue = nearer(resolvedPromise); if (isPromise(nearerValue)) { resolvedPromise = nearerValue; // shorten chain } return nearerValue; }; promise.inspect = function () { if (!resolvedPromise) { return { state: "pending" }; } return resolvedPromise.inspect(); }; if (Q.longStackSupport && hasStacks) { try { throw new Error(); } catch (e) { // NOTE: don't try to use `Error.captureStackTrace` or transfer the // accessor around; that causes memory leaks as per GH-111. Just // reify the stack trace as a string ASAP. // // At the same time, cut off the first line; it's always just // "[object Promise]\n", as per the `toString`. promise.stack = e.stack.substring(e.stack.indexOf("\n") + 1); } } // NOTE: we do the checks for `resolvedPromise` in each method, instead of // consolidating them into `become`, since otherwise we'd create new // promises with the lines `become(whatever(value))`. See e.g. GH-252. function become(newPromise) { resolvedPromise = newPromise; promise.source = newPromise; array_reduce(messages, function (undefined, message) { nextTick(function () { newPromise.promiseDispatch.apply(newPromise, message); }); }, void 0); messages = void 0; progressListeners = void 0; } deferred.promise = promise; deferred.resolve = function (value) { if (resolvedPromise) { return; } become(Q(value)); }; deferred.fulfill = function (value) { if (resolvedPromise) { return; } become(fulfill(value)); }; deferred.reject = function (reason) { if (resolvedPromise) { return; } become(reject(reason)); }; deferred.notify = function (progress) { if (resolvedPromise) { return; } array_reduce(progressListeners, function (undefined, progressListener) { nextTick(function () { progressListener(progress); }); }, void 0); }; return deferred; } /** * Creates a Node-style callback that will resolve or reject the deferred * promise. * @returns a nodeback */ defer.prototype.makeNodeResolver = function () { var self = this; return function (error, value) { if (error) { self.reject(error); } else if (arguments.length > 2) { self.resolve(array_slice(arguments, 1)); } else { self.resolve(value); } }; }; /** * @param resolver {Function} a function that returns nothing and accepts * the resolve, reject, and notify functions for a deferred. * @returns a promise that may be resolved with the given resolve and reject * functions, or rejected by a thrown exception in resolver */ Q.promise = promise; function promise(resolver) { if (typeof resolver !== "function") { throw new TypeError("resolver must be a function."); } var deferred = defer(); try { resolver(deferred.resolve, deferred.reject, deferred.notify); } catch (reason) { deferred.reject(reason); } return deferred.promise; } // XXX experimental. This method is a way to denote that a local value is // serializable and should be immediately dispatched to a remote upon request, // instead of passing a reference. Q.passByCopy = function (object) { //freeze(object); //passByCopies.set(object, true); return object; }; Promise.prototype.passByCopy = function () { //freeze(object); //passByCopies.set(object, true); return this; }; /** * If two promises eventually fulfill to the same value, promises that value, * but otherwise rejects. * @param x {Any*} * @param y {Any*} * @returns {Any*} a promise for x and y if they are the same, but a rejection * otherwise. * */ Q.join = function (x, y) { return Q(x).join(y); }; Promise.prototype.join = function (that) { return Q([this, that]).spread(function (x, y) { if (x === y) { // TODO: "===" should be Object.is or equiv return x; } else { throw new Error("Can't join: not the same: " + x + " " + y); } }); }; /** * Returns a promise for the first of an array of promises to become fulfilled. * @param answers {Array[Any*]} promises to race * @returns {Any*} the first promise to be fulfilled */ Q.race = race; function race(answerPs) { return promise(function(resolve, reject) { // Switch to this once we can assume at least ES5 // answerPs.forEach(function(answerP) { // Q(answerP).then(resolve, reject); // }); // Use this in the meantime for (var i = 0, len = answerPs.length; i < len; i++) { Q(answerPs[i]).then(resolve, reject); } }); } Promise.prototype.race = function () { return this.then(Q.race); }; /** * Constructs a Promise with a promise descriptor object and optional fallback * function. The descriptor contains methods like when(rejected), get(name), * set(name, value), post(name, args), and delete(name), which all * return either a value, a promise for a value, or a rejection. The fallback * accepts the operation name, a resolver, and any further arguments that would * have been forwarded to the appropriate method above had a method been * provided with the proper name. The API makes no guarantees about the nature * of the returned object, apart from that it is usable whereever promises are * bought and sold. */ Q.makePromise = Promise; function Promise(descriptor, fallback, inspect) { if (fallback === void 0) { fallback = function (op) { return reject(new Error( "Promise does not support operation: " + op )); }; } if (inspect === void 0) { inspect = function () { return {state: "unknown"}; }; } var promise = object_create(Promise.prototype); promise.promiseDispatch = function (resolve, op, args) { var result; try { if (descriptor[op]) { result = descriptor[op].apply(promise, args); } else { result = fallback.call(promise, op, args); } } catch (exception) { result = reject(exception); } if (resolve) { resolve(result); } }; promise.inspect = inspect; // XXX deprecated `valueOf` and `exception` support if (inspect) { var inspected = inspect(); if (inspected.state === "rejected") { promise.exception = inspected.reason; } promise.valueOf = function () { var inspected = inspect(); if (inspected.state === "pending" || inspected.state === "rejected") { return promise; } return inspected.value; }; } return promise; } Promise.prototype.toString = function () { return "[object Promise]"; }; Promise.prototype.then = function (fulfilled, rejected, progressed) { var self = this; var deferred = defer(); var done = false; // ensure the untrusted promise makes at most a // single call to one of the callbacks function _fulfilled(value) { try { return typeof fulfilled === "function" ? fulfilled(value) : value; } catch (exception) { return reject(exception); } } function _rejected(exception) { if (typeof rejected === "function") { makeStackTraceLong(exception, self); try { return rejected(exception); } catch (newException) { return reject(newException); } } return reject(exception); } function _progressed(value) { return typeof progressed === "function" ? progressed(value) : value; } nextTick(function () { self.promiseDispatch(function (value) { if (done) { return; } done = true; deferred.resolve(_fulfilled(value)); }, "when", [function (exception) { if (done) { return; } done = true; deferred.resolve(_rejected(exception)); }]); }); // Progress propagator need to be attached in the current tick. self.promiseDispatch(void 0, "when", [void 0, function (value) { var newValue; var threw = false; try { newValue = _progressed(value); } catch (e) { threw = true; if (Q.onerror) { Q.onerror(e); } else { throw e; } } if (!threw) { deferred.notify(newValue); } }]); return deferred.promise; }; /** * Registers an observer on a promise. * * Guarantees: * * 1. that fulfilled and rejected will be called only once. * 2. that either the fulfilled callback or the rejected callback will be * called, but not both. * 3. that fulfilled and rejected will not be called in this turn. * * @param value promise or immediate reference to observe * @param fulfilled function to be called with the fulfilled value * @param rejected function to be called with the rejection exception * @param progressed function to be called on any progress notifications * @return promise for the return value from the invoked callback */ Q.when = when; function when(value, fulfilled, rejected, progressed) { return Q(value).then(fulfilled, rejected, progressed); } Promise.prototype.thenResolve = function (value) { return this.then(function () { return value; }); }; Q.thenResolve = function (promise, value) { return Q(promise).thenResolve(value); }; Promise.prototype.thenReject = function (reason) { return this.then(function () { throw reason; }); }; Q.thenReject = function (promise, reason) { return Q(promise).thenReject(reason); }; /** * If an object is not a promise, it is as "near" as possible. * If a promise is rejected, it is as "near" as possible too. * If itbs a fulfilled promise, the fulfillment value is nearer. * If itbs a deferred promise and the deferred has been resolved, the * resolution is "nearer". * @param object * @returns most resolved (nearest) form of the object */ // XXX should we re-do this? Q.nearer = nearer; function nearer(value) { if (isPromise(value)) { var inspected = value.inspect(); if (inspected.state === "fulfilled") { return inspected.value; } } return value; } /** * @returns whether the given object is a promise. * Otherwise it is a fulfilled value. */ Q.isPromise = isPromise; function isPromise(object) { return isObject(object) && typeof object.promiseDispatch === "function" && typeof object.inspect === "function"; } Q.isPromiseAlike = isPromiseAlike; function isPromiseAlike(object) { return isObject(object) && typeof object.then === "function"; } /** * @returns whether the given object is a pending promise, meaning not * fulfilled or rejected. */ Q.isPending = isPending; function isPending(object) { return isPromise(object) && object.inspect().state === "pending"; } Promise.prototype.isPending = function () { return this.inspect().state === "pending"; }; /** * @returns whether the given object is a value or fulfilled * promise. */ Q.isFulfilled = isFulfilled; function isFulfilled(object) { return !isPromise(object) || object.inspect().state === "fulfilled"; } Promise.prototype.isFulfilled = function () { return this.inspect().state === "fulfilled"; }; /** * @returns whether the given object is a rejected promise. */ Q.isRejected = isRejected; function isRejected(object) { return isPromise(object) && object.inspect().state === "rejected"; } Promise.prototype.isRejected = function () { return this.inspect().state === "rejected"; }; //// BEGIN UNHANDLED REJECTION TRACKING // This promise library consumes exceptions thrown in handlers so they can be // handled by a subsequent promise. The exceptions get added to this array when // they are created, and removed when they are handled. Note that in ES6 or // shimmed environments, this would naturally be a `Set`. var unhandledReasons = []; var unhandledRejections = []; var unhandledReasonsDisplayed = false; var trackUnhandledRejections = true; function displayUnhandledReasons() { if ( !unhandledReasonsDisplayed && typeof window !== "undefined" && window.console ) { console.warn("[Q] Unhandled rejection reasons (should be empty):", unhandledReasons); } unhandledReasonsDisplayed = true; } function logUnhandledReasons() { for (var i = 0; i < unhandledReasons.length; i++) { var reason = unhandledReasons[i]; console.warn("Unhandled rejection reason:", reason); } } function resetUnhandledRejections() { unhandledReasons.length = 0; unhandledRejections.length = 0; unhandledReasonsDisplayed = false; if (!trackUnhandledRejections) { trackUnhandledRejections = true; // Show unhandled rejection reasons if Node exits without handling an // outstanding rejection. (Note that Browserify presently produces a // `process` global without the `EventEmitter` `on` method.) if (typeof process !== "undefined" && process.on) { process.on("exit", logUnhandledReasons); } } } function trackRejection(promise, reason) { if (!trackUnhandledRejections)