diff options
| author | Misko Hevery | 2012-03-23 14:03:24 -0700 | 
|---|---|---|
| committer | Misko Hevery | 2012-03-28 11:16:35 -0700 | 
| commit | 2430f52bb97fa9d682e5f028c977c5bf94c5ec38 (patch) | |
| tree | e7529b741d70199f36d52090b430510bad07f233 /src/ng/q.js | |
| parent | 944098a4e0f753f06b40c73ca3e79991cec6c2e2 (diff) | |
| download | angular.js-2430f52bb97fa9d682e5f028c977c5bf94c5ec38.tar.bz2 | |
chore(module): move files around in preparation for more modules
Diffstat (limited to 'src/ng/q.js')
| -rw-r--r-- | src/ng/q.js | 391 | 
1 files changed, 391 insertions, 0 deletions
| diff --git a/src/ng/q.js b/src/ng/q.js new file mode 100644 index 00000000..074acd1d --- /dev/null +++ b/src/ng/q.js @@ -0,0 +1,391 @@ +'use strict'; + +/** + * @ngdoc service + * @name angular.module.ng.$q + * @requires $rootScope + * + * @description + * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). + * + * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an + * interface for interacting with an object that represents the result of an action that is + * performed asynchronously, and may or may not be finished at any given point in time. + * + * From the perspective of dealing with error handling, deferred and promise apis are to + * asynchronous programing what `try`, `catch` and `throw` keywords are to synchronous programing. + * + * <pre> + *   // for the purpose of this example let's assume that variables `$q` and `scope` are + *   // available in the current lexical scope (they could have been injected or passed in). + * + *   function asyncGreet(name) { + *     var deferred = $q.defer(); + * + *     setTimeout(function() { + *       // since this fn executes async in a future turn of the event loop, we need to wrap + *       // our code into an $apply call so that the model changes are properly observed. + *       scope.$apply(function() { + *         if (okToGreet(name)) { + *           deferred.resolve('Hello, ' + name + '!'); + *         } else { + *           deferred.reject('Greeting ' + name + ' is not allowed.'); + *         } + *       }); + *     }, 1000); + * + *     return deferred.promise; + *   } + * + *   var promise = asyncGreet('Robin Hood'); + *   promise.then(function(greeting) { + *     alert('Success: ' + greeting); + *   }, function(reason) { + *     alert('Failed: ' + reason); + *   ); + * </pre> + * + * At first it might not be obvious why this extra complexity is worth the trouble. The payoff + * comes in the way of + * [guarantees that promise and deferred apis make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md). + * + * Additionally the promise api allows for composition that is very hard to do with the + * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. + * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the + * section on serial or parallel joining of promises. + * + * + * # The Deferred API + * + * A new instance of deferred is constructed by calling `$q.defer()`. + * + * The purpose of the deferred object is to expose the associated Promise instance as well as apis + * that can be used for signaling the successful or unsuccessful completion of the task. + * + * **Methods** + * + * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection + *   constructed via `$q.reject`, the promise will be rejected instead. + * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to + *   resolving it with a rejection constructed via `$q.reject`. + * + * **Properties** + * + * - promise – `{Promise}` – promise object associated with this deferred. + * + * + * # The Promise API + * + * A new promise instance is created when a deferred instance is created and can be retrieved by + * calling `deferred.promise`. + * + * The purpose of the promise object is to allow for interested parties to get access to the result + * of the deferred task when it completes. + * + * **Methods** + * + * - `then(successCallback, errorCallback)` – regardless of when the promise was or will be resolved + *   or rejected calls one of the success or error callbacks asynchronously as soon as the result + *   is available. The callbacks are called with a single argument the result or rejection reason. + * + *   This method *returns a new promise* which is resolved or rejected via the return value of the + *   `successCallback` or `errorCallback`. + * + * + * # Chaining promises + * + * Because calling `then` api of a promise returns a new derived promise, it is easily possible + * to create a chain of promises: + * + * <pre> + *   promiseB = promiseA.then(function(result) { + *     return result + 1; + *   }); + * + *   // promiseB will be resolved immediately after promiseA is resolved and it's value will be + *   // the result of promiseA incremented by 1 + * </pre> + * + * It is possible to create chains of any length and since a promise can be resolved with another + * promise (which will defer its resolution further), it is possible to pause/defer resolution of + * the promises at any point in the chain. This makes it possible to implement powerful apis like + * $http's response interceptors. + * + * + * # Differences between Kris Kowal's Q and $q + * + *  There are three main differences: + * + * - $q is integrated with the {@link angular.module.ng.$rootScope.Scope} Scope model observation + *   mechanism in angular, which means faster propagation of resolution or rejection into your + *   models and avoiding unnecessary browser repaints, which would result in flickering UI. + * - $q promises are recognized by the templating engine in angular, which means that in templates + *   you can treat promises attached to a scope as if they were the resulting values. + * - Q has many more features that $q, but that comes at a cost of bytes. $q is tiny, but contains + *   all the important functionality needed for common async tasks. + */ +function $QProvider() { + +  this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { +    return qFactory(function(callback) { +      $rootScope.$evalAsync(callback); +    }, $exceptionHandler); +  }]; +} + + +/** + * Constructs a promise manager. + * + * @param {function(function)} nextTick Function for executing functions in the next turn. + * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for + *     debugging purposes. + * @returns {object} Promise manager. + */ +function qFactory(nextTick, exceptionHandler) { + +  /** +   * @ngdoc +   * @name angular.module.ng.$q#defer +   * @methodOf angular.module.ng.$q +   * @description +   * Creates a `Deferred` object which represents a task which will finish in the future. +   * +   * @returns {Deferred} Returns a new instance of deferred. +   */ +  var defer = function() { +    var pending = [], +        value, deferred; + +    deferred = { + +      resolve: function(val) { +        if (pending) { +          var callbacks = pending; +          pending = undefined; +          value = ref(val); + +          if (callbacks.length) { +            nextTick(function() { +              var callback; +              for (var i = 0, ii = callbacks.length; i < ii; i++) { +                callback = callbacks[i]; +                value.then(callback[0], callback[1]); +              } +            }); +          } +        } +      }, + + +      reject: function(reason) { +        deferred.resolve(reject(reason)); +      }, + + +      promise: { +        then: function(callback, errback) { +          var result = defer(); + +          var wrappedCallback = function(value) { +            try { +              result.resolve((callback || defaultCallback)(value)); +            } catch(e) { +              exceptionHandler(e); +              result.reject(e); +            } +          }; + +          var wrappedErrback = function(reason) { +            try { +              result.resolve((errback || defaultErrback)(reason)); +            } catch(e) { +              exceptionHandler(e); +              result.reject(e); +            } +          }; + +          if (pending) { +            pending.push([wrappedCallback, wrappedErrback]); +          } else { +            value.then(wrappedCallback, wrappedErrback); +          } + +          return result.promise; +        } +      } +    }; + +    return deferred; +  }; + + +  var ref = function(value) { +    if (value && value.then) return value; +    return { +      then: function(callback) { +        var result = defer(); +        nextTick(function() { +          result.resolve(callback(value)); +        }); +        return result.promise; +      } +    }; +  }; + + +  /** +   * @ngdoc +   * @name angular.module.ng.$q#reject +   * @methodOf angular.module.ng.$q +   * @description +   * Creates a promise that is resolved as rejected with the specified `reason`. This api should be +   * used to forward rejection in a chain of promises. If you are dealing with the last promise in +   * a promise chain, you don't need to worry about it. +   * +   * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of +   * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via +   * a promise error callback and you want to forward the error to the promise derived from the +   * current promise, you have to "rethrow" the error by returning a rejection constructed via +   * `reject`. +   * +   * <pre> +   *   promiseB = promiseA.then(function(result) { +   *     // success: do something and resolve promiseB +   *     //          with the old or a new result +   *     return result; +   *   }, function(reason) { +   *     // error: handle the error if possible and +   *     //        resolve promiseB with newPromiseOrValue, +   *     //        otherwise forward the rejection to promiseB +   *     if (canHandle(reason)) { +   *      // handle the error and recover +   *      return newPromiseOrValue; +   *     } +   *     return $q.reject(reason); +   *   }); +   * </pre> +   * +   * @param {*} reason Constant, message, exception or an object representing the rejection reason. +   * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. +   */ +  var reject = function(reason) { +    return { +      then: function(callback, errback) { +        var result = defer(); +        nextTick(function() { +          result.resolve(errback(reason)); +        }); +        return result.promise; +      } +    }; +  }; + + +  /** +   * @ngdoc +   * @name angular.module.ng.$q#when +   * @methodOf angular.module.ng.$q +   * @description +   * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. +   * This is useful when you are dealing with on object that might or might not be a promise, or if +   * the promise comes from a source that can't be trusted. +   * +   * @param {*} value Value or a promise +   * @returns {Promise} Returns a single promise that will be resolved with an array of values, +   *   each value coresponding to the promise at the same index in the `promises` array. If any of +   *   the promises is resolved with a rejection, this resulting promise will be resolved with the +   *   same rejection. +   */ +  var when = function(value, callback, errback) { +    var result = defer(), +        done; + +    var wrappedCallback = function(value) { +      try { +        return (callback || defaultCallback)(value); +      } catch (e) { +        exceptionHandler(e); +        return reject(e); +      } +    }; + +    var wrappedErrback = function(reason) { +      try { +        return (errback || defaultErrback)(reason); +      } catch (e) { +        exceptionHandler(e); +        return reject(e); +      } +    }; + +    nextTick(function() { +      ref(value).then(function(value) { +        if (done) return; +        done = true; +        result.resolve(ref(value).then(wrappedCallback, wrappedErrback)); +      }, function(reason) { +        if (done) return; +        done = true; +        result.resolve(wrappedErrback(reason)); +      }); +    }); + +    return result.promise; +  }; + + +  function defaultCallback(value) { +    return value; +  } + + +  function defaultErrback(reason) { +    return reject(reason); +  } + + +  /** +   * @ngdoc +   * @name angular.module.ng.$q#all +   * @methodOf angular.module.ng.$q +   * @description +   * Combines multiple promises into a single promise that is resolved when all of the input +   * promises are resolved. +   * +   * @param {Array.<Promise>} promises An array of promises. +   * @returns {Promise} Returns a single promise that will be resolved with an array of values, +   *   each value coresponding to the promise at the same index in the `promises` array. If any of +   *   the promises is resolved with a rejection, this resulting promise will be resolved with the +   *   same rejection. +   */ +  function all(promises) { +    var deferred = defer(), +        counter = promises.length, +        results = []; + +    if (counter) { +      forEach(promises, function(promise, index) { +        ref(promise).then(function(value) { +          if (index in results) return; +          results[index] = value; +          if (!(--counter)) deferred.resolve(results); +        }, function(reason) { +          if (index in results) return; +          deferred.reject(reason); +        }); +      }); +    } else { +      deferred.resolve(results); +    } + +    return deferred.promise; +  } + +  return { +    defer: defer, +    reject: reject, +    when: when, +    all: all +  }; +} | 
