promise.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. (function(define){
  2. define(function(require,exports){
  3. // Kris Zyp
  4. // this is based on the CommonJS spec for promises:
  5. // http://wiki.commonjs.org/wiki/Promises
  6. // Includes convenience functions for promises, much of this is taken from Tyler Close's ref_send
  7. // and Kris Kowal's work on promises.
  8. // // MIT License
  9. // A typical usage:
  10. // A default Promise constructor can be used to create a self-resolving deferred/promise:
  11. // var Promise = require("promise").Promise;
  12. // var promise = new Promise();
  13. // asyncOperation(function(){
  14. // Promise.resolve("succesful result");
  15. // });
  16. // promise -> given to the consumer
  17. //
  18. // A consumer can use the promise
  19. // promise.then(function(result){
  20. // ... when the action is complete this is executed ...
  21. // },
  22. // function(error){
  23. // ... executed when the promise fails
  24. // });
  25. //
  26. // Alternately, a provider can create a deferred and resolve it when it completes an action.
  27. // The deferred object a promise object that provides a separation of consumer and producer to protect
  28. // promises from being fulfilled by untrusted code.
  29. // var defer = require("promise").defer;
  30. // var deferred = defer();
  31. // asyncOperation(function(){
  32. // deferred.resolve("succesful result");
  33. // });
  34. // deferred.promise -> given to the consumer
  35. //
  36. // Another way that a consumer can use the promise (using promise.then is also allowed)
  37. // var when = require("promise").when;
  38. // when(promise,function(result){
  39. // ... when the action is complete this is executed ...
  40. // },
  41. // function(error){
  42. // ... executed when the promise fails
  43. // });
  44. exports.errorTimeout = 100;
  45. var freeze = Object.freeze || function(){};
  46. /**
  47. * Default constructor that creates a self-resolving Promise. Not all promise implementations
  48. * need to use this constructor.
  49. */
  50. var Promise = function(canceller){
  51. };
  52. /**
  53. * Promise implementations must provide a "then" function.
  54. */
  55. Promise.prototype.then = function(resolvedCallback, errorCallback, progressCallback){
  56. throw new TypeError("The Promise base class is abstract, this function must be implemented by the Promise implementation");
  57. };
  58. /**
  59. * If an implementation of a promise supports a concurrency model that allows
  60. * execution to block until the promise is resolved, the wait function may be
  61. * added.
  62. */
  63. /**
  64. * If an implementation of a promise can be cancelled, it may add this function
  65. */
  66. // Promise.prototype.cancel = function(){
  67. // };
  68. Promise.prototype.get = function(propertyName){
  69. return this.then(function(value){
  70. return value[propertyName];
  71. });
  72. };
  73. Promise.prototype.put = function(propertyName, value){
  74. return this.then(function(object){
  75. return object[propertyName] = value;
  76. });
  77. };
  78. Promise.prototype.call = function(functionName /*, args */){
  79. var fnArgs = Array.prototype.slice.call(arguments, 1);
  80. return this.then(function(value){
  81. return value[functionName].apply(value, fnArgs);
  82. });
  83. };
  84. /**
  85. * This can be used to conviently resolve a promise with auto-handling of errors:
  86. * setTimeout(deferred.resolverCallback(function(){
  87. * return doSomething();
  88. * }), 100);
  89. */
  90. Promise.prototype.resolverCallback = function(callback){
  91. var self = this;
  92. return function(){
  93. try{
  94. self.resolve(callback());
  95. }catch(e){
  96. self.reject(e);
  97. }
  98. }
  99. };
  100. /** Dojo/NodeJS methods*/
  101. Promise.prototype.addCallback = function(callback){
  102. return this.then(callback);
  103. };
  104. Promise.prototype.addErrback = function(errback){
  105. return this.then(function(){}, errback);
  106. };
  107. /*Dojo methods*/
  108. Promise.prototype.addBoth = function(callback){
  109. return this.then(callback, callback);
  110. };
  111. Promise.prototype.addCallbacks = function(callback, errback){
  112. return this.then(callback, errback);
  113. };
  114. /*NodeJS method*/
  115. Promise.prototype.wait = function(){
  116. return exports.wait(this);
  117. };
  118. Deferred.prototype = Promise.prototype;
  119. // A deferred provides an API for creating and resolving a promise.
  120. exports.Promise = exports.Deferred = exports.defer = defer;
  121. function defer(canceller){
  122. return new Deferred(canceller);
  123. }
  124. // currentContext can be set to other values
  125. // and mirrors the global. We need to go off the global in case of multiple instances
  126. // of this module, which isn't rare with NPM's package policy.
  127. Object.defineProperty && Object.defineProperty(exports, "currentContext", {
  128. set: function(value){
  129. currentContext = value;
  130. },
  131. get: function(){
  132. return currentContext;
  133. }
  134. });
  135. exports.currentContext = null;
  136. function Deferred(canceller){
  137. var result, finished, isError, waiting = [], handled;
  138. var promise = this.promise = new Promise();
  139. var context = exports.currentContext;
  140. function notifyAll(value){
  141. var previousContext = exports.currentContext;
  142. if(finished){
  143. throw new Error("This deferred has already been resolved");
  144. }
  145. try{
  146. if(previousContext !== context){
  147. if(previousContext && previousContext.suspend){
  148. previousContext.suspend();
  149. }
  150. exports.currentContext = context;
  151. if(context && context.resume){
  152. context.resume();
  153. }
  154. }
  155. result = value;
  156. finished = true;
  157. for(var i = 0; i < waiting.length; i++){
  158. notify(waiting[i]);
  159. }
  160. }
  161. finally{
  162. if(previousContext !== context){
  163. if(context && context.suspend){
  164. context.suspend();
  165. }
  166. if(previousContext && previousContext.resume){
  167. previousContext.resume();
  168. }
  169. exports.currentContext = previousContext;
  170. }
  171. }
  172. }
  173. function notify(listener){
  174. var func = (isError ? listener.error : listener.resolved);
  175. if(func){
  176. handled ?
  177. (handled.handled = true) : (handled = true);
  178. try{
  179. var newResult = func(result);
  180. if(newResult && typeof newResult.then === "function"){
  181. newResult.then(listener.deferred.resolve, listener.deferred.reject);
  182. return;
  183. }
  184. listener.deferred.resolve(newResult);
  185. }
  186. catch(e){
  187. listener.deferred.reject(e);
  188. }
  189. }
  190. else{
  191. if(isError){
  192. listener.deferred.reject(result, typeof handled === "object" ? handled : (handled = {}));
  193. }
  194. else{
  195. listener.deferred.resolve.call(listener.deferred, result);
  196. }
  197. }
  198. }
  199. // calling resolve will resolve the promise
  200. this.resolve = this.callback = this.emitSuccess = function(value){
  201. notifyAll(value);
  202. };
  203. // calling error will indicate that the promise failed
  204. var reject = this.reject = this.errback = this.emitError = function(error, handledObject){
  205. if (typeof handledObject == "object") {
  206. if (handled) {
  207. handledObject.handled = true;
  208. } else {
  209. handled = handledObject;
  210. }
  211. }
  212. isError = true;
  213. notifyAll(error);
  214. if (!handledObject && typeof setTimeout !== "undefined") {
  215. if (!(typeof handled == "object" ? handled.handled : handled)) {
  216. // set the time out if it has not already been handled
  217. setTimeout(function () {
  218. if (!(typeof handled == "object" ? handled.handled : handled)) {
  219. throw error;
  220. }
  221. }, exports.errorTimeout);
  222. }
  223. }
  224. return handled;
  225. };
  226. // call progress to provide updates on the progress on the completion of the promise
  227. this.progress = function(update){
  228. for(var i = 0; i < waiting.length; i++){
  229. var progress = waiting[i].progress;
  230. progress && progress(update);
  231. }
  232. }
  233. // provide the implementation of the promise
  234. this.then = promise.then = function(resolvedCallback, errorCallback, progressCallback){
  235. var returnDeferred = new Deferred(promise.cancel);
  236. var listener = {resolved: resolvedCallback, error: errorCallback, progress: progressCallback, deferred: returnDeferred};
  237. if(finished){
  238. notify(listener);
  239. }
  240. else{
  241. waiting.push(listener);
  242. }
  243. return returnDeferred.promise;
  244. };
  245. var timeout;
  246. if(typeof setTimeout !== "undefined") {
  247. this.timeout = function (ms) {
  248. if (ms === undefined) {
  249. return timeout;
  250. }
  251. timeout = ms;
  252. setTimeout(function () {
  253. if (!finished) {
  254. if (promise.cancel) {
  255. promise.cancel(new Error("timeout"));
  256. }
  257. else {
  258. reject(new Error("timeout"));
  259. }
  260. }
  261. }, ms);
  262. return promise;
  263. };
  264. }
  265. if(canceller){
  266. this.cancel = promise.cancel = function(){
  267. var error = canceller();
  268. if(!(error instanceof Error)){
  269. error = new Error(error);
  270. }
  271. reject(error);
  272. }
  273. }
  274. freeze(promise);
  275. };
  276. function perform(value, async, sync){
  277. try{
  278. if(value && typeof value.then === "function"){
  279. value = async(value);
  280. }
  281. else{
  282. value = sync(value);
  283. }
  284. if(value && typeof value.then === "function"){
  285. return value;
  286. }
  287. var deferred = new Deferred();
  288. deferred.resolve(value);
  289. return deferred.promise;
  290. }catch(e){
  291. var deferred = new Deferred();
  292. deferred.reject(e);
  293. return deferred.promise;
  294. }
  295. }
  296. /**
  297. * Promise manager to make it easier to consume promises
  298. */
  299. function rethrow(err){ throw err; }
  300. /**
  301. * Registers an observer on a promise, always returning a promise
  302. * @param value promise or value to observe
  303. * @param resolvedCallback function to be called with the resolved value
  304. * @param rejectCallback function to be called with the rejection reason
  305. * @param progressCallback function to be called when progress is made
  306. * @return promise for the return value from the invoked callback
  307. */
  308. exports.whenPromise = function(value, resolvedCallback, rejectCallback, progressCallback){
  309. var deferred = defer();
  310. if(value && typeof value.then === "function"){
  311. value.then(function(next){
  312. deferred.resolve(next);
  313. },function(error){
  314. deferred.reject(error);
  315. });
  316. rejectCallback = rejectCallback || rethrow;
  317. }else{
  318. deferred.resolve(value);
  319. }
  320. return deferred.promise.then(resolvedCallback, rejectCallback, progressCallback);
  321. };
  322. /**
  323. * Registers an observer on a promise.
  324. * @param value promise or value to observe
  325. * @param resolvedCallback function to be called with the resolved value
  326. * @param rejectCallback function to be called with the rejection reason
  327. * @param progressCallback function to be called when progress is made
  328. * @return promise for the return value from the invoked callback or the value if it
  329. * is a non-promise value
  330. */
  331. exports.when = function(value, resolvedCallback, rejectCallback, progressCallback){
  332. if(value && typeof value.then === "function"){
  333. if(value instanceof Promise){
  334. return value.then(resolvedCallback, rejectCallback, progressCallback);
  335. }
  336. else{
  337. return exports.whenPromise(value, resolvedCallback, rejectCallback, progressCallback);
  338. }
  339. }
  340. return resolvedCallback ? resolvedCallback(value) : value;
  341. };
  342. /**
  343. * This is convenience function for catching synchronously and asynchronously thrown
  344. * errors. This is used like when() except you execute the initial action in a callback:
  345. * whenCall(function(){
  346. * return doSomethingThatMayReturnAPromise();
  347. * }, successHandler, errorHandler);
  348. */
  349. exports.whenCall = function(initialCallback, resolvedCallback, rejectCallback, progressCallback){
  350. try{
  351. return exports.when(initialCallback(), resolvedCallback, rejectCallback, progressCallback);
  352. }catch(e){
  353. return rejectCallback(e);
  354. }
  355. }
  356. /**
  357. * Gets the value of a property in a future turn.
  358. * @param target promise or value for target object
  359. * @param property name of property to get
  360. * @return promise for the property value
  361. */
  362. exports.get = function(target, property){
  363. return perform(target, function(target){
  364. return target.get(property);
  365. },
  366. function(target){
  367. return target[property]
  368. });
  369. };
  370. /**
  371. * Invokes a method in a future turn.
  372. * @param target promise or value for target object
  373. * @param methodName name of method to invoke
  374. * @param args array of invocation arguments
  375. * @return promise for the return value
  376. */
  377. exports.call = function(target, methodName, args){
  378. return perform(target, function(target){
  379. return target.call(methodName, args);
  380. },
  381. function(target){
  382. return target[methodName].apply(target, args);
  383. });
  384. };
  385. /**
  386. * Sets the value of a property in a future turn.
  387. * @param target promise or value for target object
  388. * @param property name of property to set
  389. * @param value new value of property
  390. * @return promise for the return value
  391. */
  392. exports.put = function(target, property, value){
  393. return perform(target, function(target){
  394. return target.put(property, value);
  395. },
  396. function(target){
  397. return target[property] = value;
  398. });
  399. };
  400. /**
  401. * Waits for the given promise to finish, blocking (and executing other events)
  402. * if necessary to wait for the promise to finish. If target is not a promise
  403. * it will return the target immediately. If the promise results in an reject,
  404. * that reject will be thrown.
  405. * @param target promise or value to wait for.
  406. * @return the value of the promise;
  407. */
  408. var queue;
  409. //try {
  410. // queue = require("event-loop");
  411. //}
  412. //catch (e) {}
  413. exports.wait = function(target){
  414. if(!queue){
  415. throw new Error("Can not wait, the event-queue module is not available");
  416. }
  417. if(target && typeof target.then === "function"){
  418. var isFinished, isError, result;
  419. target.then(function(value){
  420. isFinished = true;
  421. result = value;
  422. },
  423. function(error){
  424. isFinished = true;
  425. isError = true;
  426. result = error;
  427. });
  428. while(!isFinished){
  429. queue.processNextEvent(true);
  430. }
  431. if(isError){
  432. throw result;
  433. }
  434. return result;
  435. }
  436. else{
  437. return target;
  438. }
  439. };
  440. /**
  441. * Takes an array of promises and returns a promise that is fulfilled once all
  442. * the promises in the array are fulfilled
  443. * @param array The array of promises
  444. * @return the promise that is fulfilled when all the array is fulfilled, resolved to the array of results
  445. */
  446. exports.all = function(array){
  447. var deferred = new Deferred();
  448. if(Object.prototype.toString.call(array) !== '[object Array]'){
  449. array = Array.prototype.slice.call(arguments);
  450. }
  451. var fulfilled = 0, length = array.length, rejected = false;
  452. var results = [];
  453. if (length === 0) deferred.resolve(results);
  454. else {
  455. array.forEach(function(promise, index){
  456. exports.when(promise,
  457. function(value){
  458. results[index] = value;
  459. fulfilled++;
  460. if(fulfilled === length){
  461. deferred.resolve(results);
  462. }
  463. },
  464. function(error){
  465. if(!rejected){
  466. deferred.reject(error);
  467. }
  468. rejected = true;
  469. });
  470. });
  471. }
  472. return deferred.promise;
  473. };
  474. /**
  475. * Takes a hash of promises and returns a promise that is fulfilled once all
  476. * the promises in the hash keys are fulfilled
  477. * @param hash The hash of promises
  478. * @return the promise that is fulfilled when all the hash keys is fulfilled, resolved to the hash of results
  479. */
  480. exports.allKeys = function(hash){
  481. var deferred = new Deferred();
  482. var array = Object.keys(hash);
  483. var fulfilled = 0, length = array.length;
  484. var results = {};
  485. if (length === 0) deferred.resolve(results);
  486. else {
  487. array.forEach(function(key){
  488. exports.when(hash[key],
  489. function(value){
  490. results[key] = value;
  491. fulfilled++;
  492. if(fulfilled === length){
  493. deferred.resolve(results);
  494. }
  495. },
  496. deferred.reject);
  497. });
  498. }
  499. return deferred.promise;
  500. };
  501. /**
  502. * Takes an array of promises and returns a promise that is fulfilled when the first
  503. * promise in the array of promises is fulfilled
  504. * @param array The array of promises
  505. * @return a promise that is fulfilled with the value of the value of first promise to be fulfilled
  506. */
  507. exports.first = function(array){
  508. var deferred = new Deferred();
  509. if(Object.prototype.toString.call(array) !== '[object Array]'){
  510. array = Array.prototype.slice.call(arguments);
  511. }
  512. var fulfilled;
  513. array.forEach(function(promise, index){
  514. exports.when(promise, function(value){
  515. if (!fulfilled) {
  516. fulfilled = true;
  517. deferred.resolve(value);
  518. }
  519. },
  520. function(error){
  521. if (!fulfilled) {
  522. fulfilled = true;
  523. deferred.resolve(error);
  524. }
  525. });
  526. });
  527. return deferred.promise;
  528. };
  529. /**
  530. * Takes an array of asynchronous functions (that return promises) and
  531. * executes them sequentially. Each funtion is called with the return value of the last function
  532. * @param array The array of function
  533. * @param startingValue The value to pass to the first function
  534. * @return the value returned from the last function
  535. */
  536. exports.seq = function(array, startingValue){
  537. array = array.concat(); // make a copy
  538. var deferred = new Deferred();
  539. function next(value){
  540. var nextAction = array.shift();
  541. if(nextAction){
  542. exports.when(nextAction(value), next, function(error){
  543. deferred.reject(error, true);
  544. });
  545. }
  546. else {
  547. deferred.resolve(value);
  548. }
  549. }
  550. next(startingValue);
  551. return deferred.promise;
  552. };
  553. /**
  554. * Delays for a given amount of time and then fulfills the returned promise.
  555. * @param milliseconds The number of milliseconds to delay
  556. * @return A promise that will be fulfilled after the delay
  557. */
  558. if(typeof setTimeout !== "undefined") {
  559. exports.delay = function(milliseconds) {
  560. var deferred = new Deferred();
  561. setTimeout(function(){
  562. deferred.resolve();
  563. }, milliseconds);
  564. return deferred.promise;
  565. };
  566. }
  567. /**
  568. * Runs a function that takes a callback, but returns a Promise instead.
  569. * @param func node compatible async function which takes a callback as its last argument
  570. * @return promise for the return value from the callback from the function
  571. */
  572. exports.execute = function(asyncFunction){
  573. var args = Array.prototype.slice.call(arguments, 1);
  574. var deferred = new Deferred();
  575. args.push(function(error, result){
  576. if(error) {
  577. deferred.emitError(error);
  578. }
  579. else {
  580. if(arguments.length > 2){
  581. // if there are multiple success values, we return an array
  582. Array.prototype.shift.call(arguments, 1);
  583. deferred.emitSuccess(arguments);
  584. }
  585. else{
  586. deferred.emitSuccess(result);
  587. }
  588. }
  589. });
  590. asyncFunction.apply(this, args);
  591. return deferred.promise;
  592. };
  593. function isGeneratorFunction(obj){
  594. return obj && obj.constructor && 'GeneratorFunction' == obj.constructor.name;
  595. }
  596. /**
  597. * Promise-based coroutine trampoline
  598. * Adapted from https://github.com/deanlandolt/copromise/blob/master/copromise.js
  599. */
  600. function run(coroutine){
  601. var deferred = defer();
  602. (function next(value, exception) {
  603. var result;
  604. try {
  605. result = exception ? coroutine.throw(value) : coroutine.next(value);
  606. }
  607. catch (error) {
  608. return deferred.reject(error);
  609. }
  610. if (result.done) return deferred.resolve(result.value);
  611. exports.when(result.value, next, function(error) {
  612. next(error, true);
  613. });
  614. })();
  615. return deferred.promise;
  616. };
  617. /**
  618. * Creates a task from a coroutine, provided as generator. The `yield` function can be provided
  619. * a promise (or any value) to wait on, and the value will be provided when the promise resolves.
  620. * @param coroutine generator or generator function to treat as a coroutine
  621. * @return promise for the return value from the coroutine
  622. */
  623. exports.spawn = function(coroutine){
  624. if (isGeneratorFunction(coroutine)) {
  625. coroutine = coroutine();
  626. }
  627. return run(coroutine);
  628. }
  629. /**
  630. * Converts a Node async function to a promise returning function
  631. * @param func node compatible async function which takes a callback as its last argument
  632. * @return A function that returns a promise
  633. */
  634. exports.convertNodeAsyncFunction = function(asyncFunction, callbackNotDeclared){
  635. var arity = asyncFunction.length;
  636. return function(){
  637. var deferred = new Deferred();
  638. if(callbackNotDeclared){
  639. arity = arguments.length + 1;
  640. }
  641. arguments.length = arity;
  642. arguments[arity - 1] = function(error, result){
  643. if(error) {
  644. deferred.emitError(error);
  645. }
  646. else {
  647. if(arguments.length > 2){
  648. // if there are multiple success values, we return an array
  649. Array.prototype.shift.call(arguments, 1);
  650. deferred.emitSuccess(arguments);
  651. }
  652. else{
  653. deferred.emitSuccess(result);
  654. }
  655. }
  656. };
  657. asyncFunction.apply(this, arguments);
  658. return deferred.promise;
  659. };
  660. };
  661. /**
  662. * Returns a promise. If the object is already a Promise it is returned; otherwise
  663. * the object is wrapped in a Promise.
  664. * @param value The value to be treated as a Promise
  665. * @return A promise wrapping the original value
  666. */
  667. exports.as = function(value){
  668. if (value instanceof Promise) {
  669. return value;
  670. } else {
  671. var ret = defer();
  672. ret.resolve(value);
  673. return ret;
  674. }
  675. };
  676. });
  677. })(typeof define!="undefined"?define:function(factory){factory(require,exports)});