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(i); 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 /** 160 * Applies the same function that returns a promise to both the callback and errback. 161 * 162 * @param {Function} callback function to call. This function must return a Promise 163 * 164 * @return {comb.Promise} a promise to continue chaining or to resolve with. 165 * 166 */ 167 chainBoth:function(callback){ 168 var p = this.chain(callback); 169 this.addErrback(function(results){ 170 callback.call(this, results).then(hitch(p, "callback"), hitch(p, "errback")); 171 }); 172 return p; 173 } 174 175 176 } 177 }); 178 179 180 var PromiseList = define(Promise, { 181 instance:{ 182 /** @lends comb.PromiseList.prototype */ 183 184 /*@private*/ 185 __results:null, 186 187 /*@private*/ 188 __errors:null, 189 190 /*@private*/ 191 __promiseLength:0, 192 193 /*@private*/ 194 __defLength:0, 195 196 /*@private*/ 197 __firedLength:0, 198 199 normalizeResults:false, 200 201 /** 202 * 203 * PromiseList object used for handling a list of Promises 204 * @example var myFunc = function(){ 205 * var promise = new Promise(); 206 * //callback the promise after 10 Secs 207 * setTimeout(hitch(promise, "callback"), 10000); 208 * return promise; 209 * } 210 * var myFunc2 = function(){ 211 * var promises =[]; 212 * for(var i = 0; i < 10; i++){ 213 * promises.push(myFunc); 214 * } 215 * //create a new promise list with all 10 promises 216 * return new PromiseList(promises); 217 * } 218 * var pl = new comb.PomiseList([myFunc(), myFunc2()]); 219 * pl.then(do something...) 220 * pl.addCallback(do something...) 221 * pl.cain(myfunc).then(do something...) 222 * pl.cain(myfunc).addCallback(do something...) 223 * 224 * @param {comb.Promise[]} [defs=[]] the list of promises 225 * @constructs 226 * @augments comb.Promise 227 * @memberOf comb 228 * */ 229 constructor:function(defs, normalizeResults){ 230 this.__defLength = defs.length; 231 this.__errors = []; 232 this.__results = []; 233 this.normalizeResults = base.isBoolean(normalizeResults) ? normalizeResults : false; 234 this._super(arguments); 235 defs.forEach(this.__addPromise, this); 236 }, 237 238 /** 239 * Add a promise to our chain 240 * @private 241 * @param promise the promise to add to our chain 242 * @param i the index of the promise in our chain 243 */ 244 __addPromise:function(promise, i){ 245 promise.addCallback(hitch(this, function(){ 246 var args = Array.prototype.slice.call(arguments); 247 args.unshift(i); 248 this.callback.apply(this, args); 249 })); 250 promise.addErrback(hitch(this, function(){ 251 var args = Array.prototype.slice.call(arguments); 252 args.unshift(i); 253 this.errback.apply(this, args); 254 })); 255 }, 256 257 /** 258 * Resolves the promise 259 * @private 260 */ 261 __resolve:function(){ 262 if (!this.__fired) { 263 this.__fired = true; 264 var cbs = this.__errors.length ? this.__errorCbs : this.__cbs, 265 len = cbs.length, i, 266 results = this.__errors.length ? this.__errors : this.__results; 267 for (i = 0; i < len; i++) { 268 cbs[i].call(this, results); 269 } 270 } 271 }, 272 273 addCallback:function(cb){ 274 if (cb) { 275 if (this.__fired && !this.__errors.length) { 276 cb.call(this, this.__results); 277 } else { 278 this.__cbs.push(cb); 279 } 280 } 281 return this; 282 }, 283 284 addErrback:function(cb){ 285 if (cb) { 286 if (this.__fired && this.__errors.length) { 287 cb.call(this, this.__errors); 288 } else { 289 this.__errorCbs.push(cb); 290 } 291 } 292 return this; 293 }, 294 295 296 callback:function(i){ 297 if (this.__fired) { 298 throw new Error("Already fired!"); 299 } 300 var args = base.argsToArray(arguments); 301 if (this.normalizeResults) { 302 args = args.slice(1); 303 args = args.length == 1 ? args.pop() : args; 304 } 305 this.__results[i] = args; 306 this.__firedLength++; 307 if (this.__firedLength == this.__defLength) { 308 this.__resolve(); 309 } 310 return this; 311 }, 312 313 314 errback:function(i){ 315 if (this.__fired) { 316 throw new Error("Already fired!"); 317 } 318 var args = base.argsToArray(arguments); 319 if (this.normalizeResults) { 320 args = args.slice(1); 321 args = args.length == 1 ? args.pop() : args; 322 } 323 this.__errors[i] = args; 324 this.__firedLength++; 325 if (this.__firedLength == this.__defLength) { 326 this.__resolve(); 327 } 328 return this; 329 } 330 331 } 332 }); 333 exports.Promise = Promise; 334 exports.PromiseList = PromiseList; 335 336 337 var createHandler = function(obj, stack){ 338 return { 339 340 delete:function(name){ 341 stack.push([obj, ["__delete__", [name]], [], obj]); 342 return true; 343 }, 344 get:function(receiver, name){ 345 var newStack = []; 346 var handle = getHandler({}, newStack); 347 var wrapper = base.createFunctionWrapper(handle, function(m){ 348 var i = stack[stack.indexOf(item)]; 349 var args = base.argsToArray(arguments); 350 i[1].push(name === "__apply__" || name === "__new__" ? base.array.flatten(args) : args); 351 return wrapper; 352 }); 353 var item = [obj, [name], newStack, wrapper]; 354 stack.push(item); 355 return wrapper; 356 }, 357 set:function(receiver, name, val){ 358 stack.push([obj, ["__set__", [name, val]], [], obj]); 359 return true; 360 }, // bad behavior when set fails in non-strict mode 361 enumerate:function(){ 362 stack.push([obj, ["__enumerate__"], [], obj]); 363 return []; 364 } 365 366 } 367 }; 368 369 var functionHandler = function(handle, stack){ 370 var wrapper = base.createFunctionWrapper(handle, function(m){ 371 return handle.__apply__(base.argsToArray(arguments)); 372 }, function(){ 373 return handle.__new__(base.argsToArray(arguments)); 374 }); 375 return wrapper; 376 }; 377 378 var getHandler = function(obj, stack){ 379 var prox = base.isObject(obj) || base.isFunction(obj) ? base.handlerProxy({}, createHandler(obj, stack)) : obj, ret; 380 if (base.isFunction(obj)) { 381 return functionHandler(prox, stack); 382 } else { 383 ret = prox; 384 } 385 return ret; 386 }; 387 388 var getValueFromArrayMap = function(arg, pMap, handles){ 389 var result; 390 if (base.isProxy(arg)) { 391 var results = pMap.filter(function(p){ 392 return p[0] === arg; 393 }); 394 if (results.length) { 395 result = results[0][1]; 396 } else { 397 !results.length && (results = handles.filter(function(h){ 398 return h[1] === arg; 399 })); 400 if (results.length) { 401 result = results[0][0]; 402 } 403 } 404 } else if (base.isHash(arg) || base.isArray(arg)) { 405 result = arg; 406 for (var i in arg) { 407 var a = arg[i]; 408 arg[i] = getValueFromArrayMap(a, pMap, handles); 409 } 410 } else { 411 result = arg; 412 } 413 return result; 414 }; 415 416 var executeStack = function(stack, handles, pMap){ 417 var ret = new Promise(), l = stack.length, results = []; 418 pMap = pMap || []; 419 var errored = false; 420 if (l) { 421 var exec = function(i){ 422 if (i < l) { 423 var curr = stack[i], obj = getValueFromArrayMap(curr[0], pMap, handles), m = curr[1], name = m[0], args = m[1], subStack = curr[2], handle = curr[3]; 424 var p; 425 try { 426 if (name === "__set__") { 427 p = (obj[args[0]] = getValueFromArrayMap(args[1], pMap, handles)); 428 } else if (name === "__delete__") { 429 p = delete obj[args[0]]; 430 } else if (name === "__keys__" || name === "__enumerate__") { 431 throw new Error(name.replace(/^__|__$/g, "") + " is not supported"); 432 } else if (name === "__apply__") { 433 p = (obj.apply(obj, getValueFromArrayMap(args, pMap, handles))); 434 } else if (name === "__new__") { 435 try { 436 p = new obj(); 437 } catch (ignore) { 438 } 439 obj.apply(p, getValueFromArrayMap(args, pMap, handles)); 440 } else if (base.isUndefined(args)) { 441 p = obj[name]; 442 } else { 443 var f; 444 if (!base.isUndefined((f = obj[name])) && base.isFunction(f)) { 445 p = f.apply(obj, args.map(function(arg){ 446 return getValueFromArrayMap(arg, pMap, handles); 447 })); 448 } else { 449 //Purposely call to throw an ERROR!!!! 450 obj[name](); 451 } 452 } 453 } catch (e) { 454 errored = true; 455 return ret.errback(e); 456 } 457 exports.when(p, hitch(this, function(res){ 458 if (subStack.length) { 459 subStack.forEach(function(ss){ 460 ss[0] = res; 461 }); 462 executeStack(subStack, handles, pMap).then(hitch(this, function(sres){ 463 pMap.push([handle, res]); 464 results.push(sres); 465 !errored && exec(++i); 466 }), hitch(ret, "errback")); 467 } else { 468 pMap.push([handle, res]); 469 results.push(res); 470 !errored && exec(++i); 471 } 472 }), hitch(ret, "errback")) 473 } else { 474 !errored && ret.callback(results, pMap); 475 } 476 }; 477 exec(0); 478 } else { 479 ret.callback(results, pMap); 480 } 481 return ret; 482 }; 483 484 base.merge(exports, { 485 /** 486 * @lends comb 487 */ 488 489 /** 490 * This method allows one to code asynchronous code in a synchronous manner. 491 * 492 * <p> 493 * <b> 494 * Using Object.define[rest of name] on objects passed will result in unexpected behavior.</br> 495 * Enumerating passed in object keys is not currently supported. i.e for in loops on objects. 496 * using array enumeration methods will work though!! 497 * </b> 498 * </p> 499 * 500 * @example 501 * 502 * var staticValueFunction = function (value) { 503 * return comb.argsToArray(arguments).join(" "); 504 * }; 505 * 506 * var promiseValueFunction = function (value) { 507 * var ret = new comb.Promise(); 508 * setTimeout(comb.hitch(ret, "callback", comb.argsToArray(arguments).join(" ")), 100); 509 * return ret; 510 * }; 511 * 512 * var hash = { 513 * staticValueFunction:staticValueFunction, 514 * promiseValueFunction:promiseValueFunction 515 * }; 516 * 517 * var p = comb.executeInOrder(hash, staticValueFunction, promiseValueFunction, function (hash, staticValueFunction, promiseValueFunction) { 518 * var toBe = staticValueFunction(promiseValueFunction("to"), "be"); 519 * var notToBe = hash.promiseValueFunction("or", hash.staticValueFunction("not", toBe)); 520 * return hash.promiseValueFunction(toBe, notToBe); 521 * }); 522 * p.addCallback(function(ret){ 523 * console.log(ret); //=>"to be or not to be" 524 * }); 525 * @param {Object...} args variable number of objects. 526 * @param {Function} cb the function to callback to execute in order 527 * @returns comb.Promise 528 * 529 */ 530 executeInOrder:function(args, cb){ 531 args = base.argsToArray(arguments); 532 cb = base.isFunction(args[args.length - 1]) ? args.pop() : null; 533 var ret = new Promise(); 534 if (cb) { 535 var stack = []; 536 var newArgs = args.map(function(a){ 537 return [a, getHandler(a, stack)]; 538 }); 539 var cbRet = cb.apply(null, newArgs.map(function(h){ 540 return h[1]; 541 })); 542 executeStack(stack, newArgs).then(function(results, pMap){ 543 if (base.isUndefined(cbRet)) { 544 ret.callback(results); 545 } 546 else { 547 var cbResults; 548 if (base.isArray(cbRet)) { 549 cbResults = cbRet.map( 550 function(arg){ 551 return getValueFromArrayMap(arg, pMap, newArgs); 552 }).filter(function(r){ 553 return !base.isUndefined(r) 554 }); 555 } else if (base.isHash(cbRet)) { 556 cbResults = {}; 557 for (var i in cbRet) { 558 cbResults[i] = getValueFromArrayMap(cbRet[i], pMap, newArgs); 559 } 560 } else if (base.isProxy(cbRet)) { 561 cbResults = getValueFromArrayMap(cbRet, pMap, newArgs); 562 } else { 563 cbResults = cbRet; 564 } 565 ret.callback(cbResults); 566 } 567 }, hitch(ret, "errback")); 568 569 } else { 570 ret.callback(); 571 } 572 return ret; 573 }, 574 575 /** 576 * Tests if an object is like a promise (i.e. it contains then, addCallback, addErrback) 577 * @param obj object to test 578 */ 579 isPromiseLike:function(obj){ 580 return !base.isUndefinedOrNull(obj) && (base.isInstanceOf(obj, Promise) || (base.isFunction(obj.then) && base.isFunction(obj.addCallback) && base.isFunction(obj.addErrback))); 581 }, 582 583 /** 584 * Waits for promise and non promise values to resolve and fires callback or errback appropriately. 585 * 586 * @example 587 * var a = "hello"; 588 * var b = new comb.Promise().callback(world); 589 * comb.when(a, b) => called back with ["hello", "world"]; 590 * 591 * @param {Anything...} args variable number of arguments to wait for 592 * @param {Function} cb the callback function 593 * @param {Function} eb the errback function 594 * @returns {comb.Promise} a promise that is fired when all values have resolved 595 */ 596 when:function(args, cb, eb){ 597 var args = base.argsToArray(arguments), p; 598 eb = base.isFunction(args[args.length - 1]) ? args.pop() : null; 599 cb = base.isFunction(args[args.length - 1]) ? args.pop() : null; 600 if (eb && !cb) { 601 cb = eb; 602 eb = null; 603 } 604 if (!args.length) { 605 p = new Promise().callback(args); 606 } else if (args.length == 1) { 607 args = args.pop(); 608 p = exports.isPromiseLike(args) ? args : new Promise().callback(args); 609 } else { 610 var p = new PromiseList(args.map(function(a){ 611 return exports.isPromiseLike(a) ? a : new Promise().callback(a); 612 }), true); 613 } 614 if (cb) { 615 p.addCallback(cb); 616 } 617 if (eb) { 618 p.addErrback(eb); 619 } 620 return p; 621 622 } 623 624 }); 625