1 var hitch = require("./base/functions").hitch, 2 define = require("./define").define, base = require("./base"); 3 4 var Promise = define(null, { 5 instance : { 6 /** @lends comb.Promise.prototype */ 7 __fired : false, 8 9 __results : null, 10 11 __error : null, 12 13 __errorCbs : null, 14 15 __cbs : null, 16 17 /** 18 * Promise object used for handling a thread 19 *@example 20 * var myFunc = function(){ 21 * var promise = new Promise(); 22 * //callback the promise after 10 Secs 23 * setTimeout(hitch(promise, "callback"), 10000); 24 * return promise; 25 * } 26 * var myFunc2 = function(){ 27 * var promises =[]; 28 * for(var i = 0; i < 10; i++){ 29 * promises.push(myFunc); 30 * } 31 * //create a new promise list with all 10 promises 32 * return new PromiseList(promises); 33 * } 34 * 35 * myFunc.then(do something...) 36 * myFunc.addCallback(do something...) 37 * myFunc.cain(myfunc).then(do something...) 38 * myFunc.cain(myfunc).addCallback(do something...) 39 * 40 * myFunc2.then(do something...) 41 * myFunc2.addCallback(do something...) 42 * myFunc2.cain(myfunc).then(do something...) 43 * myFunc2.cain(myfunc).addCallback(do something...) 44 * @memberOf comb 45 * @constructs 46 */ 47 constructor : function() { 48 this.__errorCbs = []; 49 this.__cbs = []; 50 }, 51 /** 52 * @private 53 */ 54 __resolve : function() { 55 if (!this.__fired) { 56 this.__fired = true; 57 var cbs = this.__error ? this.__errorCbs : this.__cbs, 58 len = cbs.length, i, 59 results = this.__error || this.__results; 60 for (i = 0; i < len; i++) { 61 cbs[i].apply(this, results); 62 } 63 } 64 }, 65 66 /** 67 * Add a callback to the callback chain of the promise 68 * 69 * 70 * @param {Function} cb the function to callback when the promise is resolved 71 */ 72 addCallback : function(cb) { 73 if (cb) { 74 if (this.__fired && this.__results) { 75 cb.apply(this, this.__results); 76 } else { 77 this.__cbs.push(cb); 78 } 79 } 80 return this; 81 }, 82 83 84 /** 85 * Add a callback to the errback chain of the promise 86 * 87 * @param {Function} cb the function to callback when the promise errors 88 */ 89 addErrback : function(cb) { 90 if (cb) { 91 if (this.__fired && this.__error) { 92 cb.apply(this, this.__error); 93 } else { 94 this.__errorCbs.push(cb); 95 } 96 } 97 }, 98 99 both : function(cb) { 100 this.addCallback(cb); 101 this.addErrback(cb); 102 return this; 103 }, 104 105 /** 106 * When called all functions registered as callbacks are called with the passed in results. 107 * 108 * @param anything variable number of results to pass back to listeners of the promise 109 */ 110 callback : function() { 111 if (this.__fired) { 112 throw new Error("Already fired!"); 113 } 114 this.__results = Array.prototype.slice.call(arguments); 115 this.__resolve(); 116 return this; 117 }, 118 119 /** 120 * When called all functions registered as errbacks are called with the passed in error(s) 121 * 122 * @param anything variable number of errors to pass back to listeners of the promise 123 */ 124 errback : function(i) { 125 if (this.__fired) { 126 throw new Error("Already fired!"); 127 } 128 this.__error = Array.prototype.slice.call(arguments); 129 this.__resolve(); 130 return this; 131 }, 132 133 /** 134 * Call to specify action to take after promise completes or errors 135 * 136 * @param {Function} [callback=null] function to call after the promise completes successfully 137 * @param {Function} [errback=null] function to call if the promise errors 138 */ 139 then : function(callback, errback) { 140 this.addCallback(callback); 141 this.addErrback(errback); 142 return this; 143 }, 144 145 /** 146 * Call to chaining of promises 147 * @param callback method to call that returns a promise to call after this one completes. 148 * @param errback method to call if this promise errors. 149 */ 150 chain : function(callback, errback) { 151 var promise = new Promise(); 152 this.addCallback(function(results) { 153 callback.call(this, results).then(hitch(promise, "callback"), hitch(promise, "errback")); 154 }); 155 this.addErrback(errback); 156 return promise; 157 }, 158 159 chainBoth : function(callback) { 160 var p = this.chain(callback); 161 this.addErrback(function(results) { 162 callback.call(this, results).then(hitch(p, "callback"), hitch(p, "errback")); 163 }); 164 return p; 165 } 166 167 168 } 169 }); 170 171 172 var PromiseList = define(Promise, { 173 instance : { 174 /** @lends comb.PromiseList.prototype */ 175 176 /*@private*/ 177 __results : null, 178 179 /*@private*/ 180 __errors : null, 181 182 /*@private*/ 183 __promiseLength : 0, 184 185 /*@private*/ 186 __defLength : 0, 187 188 /*@private*/ 189 __firedLength : 0, 190 191 /** 192 * 193 * PromiseList object used for handling a list of Promises 194 * @example var myFunc = function(){ 195 * var promise = new Promise(); 196 * //callback the promise after 10 Secs 197 * setTimeout(hitch(promise, "callback"), 10000); 198 * return promise; 199 * } 200 * var myFunc2 = function(){ 201 * var promises =[]; 202 * for(var i = 0; i < 10; i++){ 203 * promises.push(myFunc); 204 * } 205 * //create a new promise list with all 10 promises 206 * return new PromiseList(promises); 207 * } 208 * var pl = new comb.PomiseList([myFunc(), myFunc2()]); 209 * pl.then(do something...) 210 * pl.addCallback(do something...) 211 * pl.cain(myfunc).then(do something...) 212 * pl.cain(myfunc).addCallback(do something...) 213 * 214 * @param {comb.Promise[]} [defs=[]] the list of promises 215 * @constructs 216 * @augments comb.Promise 217 * @memberOf comb 218 * */ 219 constructor : function(defs) { 220 this.__defLength = defs.length; 221 this.__errors = []; 222 this.__results = []; 223 this._super(arguments); 224 defs.forEach(this.__addPromise, this); 225 }, 226 227 /** 228 * Add a promise to our chain 229 * @private 230 * @param promise the promise to add to our chain 231 * @param i the index of the promise in our chain 232 */ 233 __addPromise : function(promise, i) { 234 promise.addCallback(hitch(this, function() { 235 var args = Array.prototype.slice.call(arguments); 236 args.unshift(i); 237 this.callback.apply(this, args); 238 })); 239 promise.addErrback(hitch(this, function() { 240 var args = Array.prototype.slice.call(arguments); 241 args.unshift(i); 242 this.errback.apply(this, args); 243 })); 244 }, 245 246 /** 247 * Resolves the promise 248 * @private 249 */ 250 __resolve : function() { 251 if (!this.__fired) { 252 this.__fired = true; 253 var cbs = this.__errors.length ? this.__errorCbs : this.__cbs, 254 len = cbs.length, i, 255 results = this.__errors.length ? this.__errors : this.__results; 256 for (i = 0; i < len; i++) { 257 cbs[i].call(this, results); 258 } 259 } 260 }, 261 262 addCallback : function(cb) { 263 if (cb) { 264 if (this.__fired && !this.__errors.length) { 265 cb.call(this, this.__results); 266 } else { 267 this.__cbs.push(cb); 268 } 269 } 270 return this; 271 }, 272 273 addErrback : function(cb) { 274 if (cb) { 275 if (this.__fired && this.__errors.length) { 276 cb.call(this, this.__errors); 277 } else { 278 this.__errorCbs.push(cb); 279 } 280 } 281 return this; 282 }, 283 284 285 callback : function(i) { 286 if (this.__fired) { 287 throw new Error("Already fired!"); 288 } 289 this.__results[i] = (Array.prototype.slice.call(arguments)); 290 this.__firedLength++; 291 if (this.__firedLength == this.__defLength) { 292 this.__resolve(); 293 } 294 return this; 295 }, 296 297 298 errback : function(i) { 299 if (this.__fired) { 300 throw new Error("Already fired!"); 301 } 302 this.__errors[i] = Array.prototype.slice.call(arguments); 303 this.__firedLength++; 304 if (this.__firedLength == this.__defLength) { 305 this.__resolve(); 306 } 307 return this; 308 } 309 310 } 311 }); 312 exports.Promise = Promise; 313 exports.PromiseList = PromiseList; 314 315 var createHandler = function(obj, stack) { 316 return { 317 getOwnPropertyDescriptor: function(name) { 318 var desc = Object.getOwnPropertyDescriptor(obj, name); 319 // a trapping proxy's properties must always be configurable 320 if (desc !== undefined) { 321 desc.configurable = true; 322 } 323 return desc; 324 }, 325 getPropertyDescriptor: function(name) { 326 var desc = Object.getPropertyDescriptor(obj, name); // not in ES5 327 // a trapping proxy's properties must always be configurable 328 if (desc !== undefined) { 329 desc.configurable = true; 330 } 331 return desc; 332 }, 333 getOwnPropertyNames: function() { 334 throw new Error("Cannot get own property names") 335 }, 336 getPropertyNames: function() { 337 throw new Error("Cannot get property names") 338 }, 339 defineProperty: function(name, desc) { 340 throw new Error("Cannot define property") 341 }, 342 delete: function(name) { 343 throw new Error("Cannot delete"); 344 }, 345 fix: function() { 346 throw new Error("Cannot fix"); 347 }, 348 349 has: function(name) { 350 return name in obj; 351 }, 352 hasOwn: function(name) { 353 return ({}).hasOwnProperty.call(obj, name); 354 }, 355 get: function(receiver, name) { 356 var newStack = []; 357 var handle = getHandler({}, newStack); 358 var wrapper = base.createFunctionWrapper(handle, function(m) { 359 var i = stack[stack.indexOf(item)]; 360 i[1].push(base.argsToArray(arguments)); 361 return wrapper; 362 }); 363 var item = [obj, [name], newStack, wrapper]; 364 stack.push(item); 365 return wrapper; 366 }, 367 set: function(receiver, name, val) { 368 stack.push([obj, ["__set__", [name, val]], [], {}]); 369 return true; 370 }, // bad behavior when set fails in non-strict mode 371 enumerate: function() { 372 throw new Error("Cannot enumerate"); 373 }, 374 keys: function() { 375 throw new Error("Cannot retrieve keys") 376 } 377 378 } 379 }; 380 381 var getHandler = function(obj, stack) { 382 return base.isObject(obj) ? base.handlerProxy({}, createHandler(obj, stack)) : obj; 383 }; 384 385 var getValueFromArrayMap = function(arg, pMap) { 386 var result; 387 if (base.isProxy(arg)) { 388 var results = pMap.filter(function(p) { 389 return p[0] == arg; 390 }); 391 if (results.length) { 392 result = results[0][1]; 393 } 394 } else { 395 result = arg; 396 } 397 return result; 398 }; 399 400 var executeStack = function(stack, pMap) { 401 var ret = new Promise(), l = stack.length, results = []; 402 pMap = pMap || []; 403 var errored = false; 404 if (l) { 405 var exec = function(i) { 406 if (i < l) { 407 var curr = stack[i], obj = getValueFromArrayMap(curr[0], pMap), m = curr[1], name = m[0], args = m[1], subStack = curr[2], handle = curr[3]; 408 var p; 409 try { 410 if (name == "__set__") { 411 p = obj[args[0]] = getValueFromArrayMap(args[1], pMap); 412 } else if (base.isUndefined(args)) { 413 p = obj[name]; 414 } else { 415 var f; 416 if (!base.isUndefined((f = obj[name])) && base.isFunction(f)) { 417 p = f.apply(obj, args.map(function(arg) { 418 return getValueFromArrayMap(arg, pMap); 419 })); 420 } 421 } 422 } catch(e) { 423 errored = true; 424 return ret.errback(new Error(e)); 425 } 426 427 if (base.isInstanceOf(p, Promise)) { 428 p.then(hitch(this, function(res) { 429 if (subStack.length) { 430 subStack.forEach(function(ss) { 431 ss[0] = res; 432 }); 433 executeStack(subStack, pMap).then(hitch(this, function(sres) { 434 pMap.push([handle, res]); 435 results.push(sres); 436 !errored && exec(++i); 437 }), hitch(ret, "errback")); 438 } else { 439 pMap.push([handle, res]); 440 results.push(res); 441 !errored && exec(++i); 442 } 443 }), hitch(ret, "errback")); 444 } else { 445 if (subStack.length) { 446 subStack.forEach(function(ss) { 447 ss[0] = p; 448 }); 449 executeStack(subStack, pMap).then(hitch(this, function(sres) { 450 pMap.push([handle, p]); 451 results.push(sres); 452 !errored && exec(++i); 453 }), hitch(ret, "errback")); 454 } else { 455 pMap.push([handle, p]); 456 results.push(p); 457 !errored && exec(++i); 458 } 459 } 460 } else { 461 !errored && ret.callback(results, pMap); 462 } 463 }; 464 exec(0); 465 } else { 466 ret.callback(results, pMap); 467 } 468 return ret; 469 }; 470 471 base.merge(exports, { 472 /** 473 * @lends comb 474 */ 475 476 /** 477 * This method allows one to code asynchronous code in a synchronous manner. 478 * 479 * @example 480 * var a = { 481 * promiseMethod : function(){ 482 * var ret = new Promise(); 483 * setTimeout(comb.hitch(ret, "callback", 10), 1000); 484 * return ret; 485 * } 486 * } 487 * 488 * var p = comb.executeInOrder(a, function(a){ 489 * var x = a.promiseMethod(); 490 * var y = a.promiseMethod(); 491 * return [x, y]; 492 * }); 493 * 494 * p.then(function(ret){ 495 * console.log(ret) //=>[10, 10]; 496 * }); 497 * @param {Object...} args variable number of objects. 498 * @param {Funciton} cb the function to callback to execute in order 499 * @returns comb.Promise 500 * 501 */ 502 executeInOrder : function(args, cb) { 503 args = base.argsToArray(arguments); 504 cb = base.isFunction(args[args.length - 1]) ? args.pop() : null; 505 var ret = new Promise(); 506 if (cb) { 507 var stack = []; 508 var newArgs = args.map(function(a) { 509 return getHandler(a, stack); 510 }); 511 var cbRet = cb.apply(null, newArgs); 512 executeStack(stack).then(function(results, pMap) { 513 if (base.isUndefined(cbRet)) { 514 ret.callback(results); 515 } 516 else { 517 var cbResults; 518 if (base.isArray(cbRet)) { 519 cbResults = cbRet.map( 520 function(arg) { 521 return getValueFromArrayMap(arg, pMap); 522 }).filter(function(r) { 523 return !base.isUndefined(r) 524 }); 525 } else if (base.isHash(cbRet)) { 526 cbResults = {}; 527 for (var i in cbRet) { 528 cbResults[i] = getValueFromArrayMap(cbRet[i], pMap); 529 } 530 } else if (base.isProxy(cbRet)) { 531 cbResults = getValueFromArrayMap(cbRet, pMap); 532 } else { 533 cbResults = cbRet; 534 } 535 ret.callback(cbResults); 536 } 537 }, hitch(ret, "errback")); 538 539 } else { 540 ret.callback(); 541 } 542 return ret; 543 }; 544 545 }) 546 ;