text: /**
   * @ngdoc object
   * @name ui.router.util.$resolve
   *
   * @requires $q
   * @requires $injector
   *
   * @description
   * Manages resolution of (acyclic) graphs of promises.
   */
  $Resolve.$inject = ['$q', '$injector'];
  function $Resolve($q, $injector) {
    var VISIT_IN_PROGRESS = 1,
      VISIT_DONE = 2,
      NOTHING = {},
      NO_DEPENDENCIES = [],
      NO_LOCALS = NOTHING,
      NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING });
  
    /**
     * @ngdoc function
     * @name ui.router.util.$resolve#study
     * @methodOf ui.router.util.$resolve
     *
     * @description
     * Studies a set of invocables that are likely to be used multiple times.
     * <pre>
     * $resolve.study(invocables)(locals, parent, self)
     * </pre>
     * is equivalent to
     * <pre>
     * $resolve.resolve(invocables, locals, parent, self)
     * </pre>
     * but the former is more efficient (in fact `resolve` just calls `study`
     * internally).
     *
     * @param {object} invocables Invocable objects
     * @return {function} a function to pass in locals, parent and self
     */
    this.study = function (invocables) {
      if (!isObject(invocables)) throw new Error("'invocables' must be an object");
      var invocableKeys = objectKeys(invocables || {});
  
      // Perform a topological sort of invocables to build an ordered plan
      var plan = [],
        cycle = [],
        visited = {};
      function visit(value, key) {
        if (visited[key] === VISIT_DONE) return;
  
        cycle.push(key);
        if (visited[key] === VISIT_IN_PROGRESS) {
          cycle.splice(0, indexOf(cycle, key));
          throw new Error('Cyclic dependency: ' + cycle.join(' -> '));
        }
        visited[key] = VISIT_IN_PROGRESS;
  
        if (isString(value)) {
          plan.push(
            key,
            [
              function () {
                return $injector.get(value);
              },
            ],
            NO_DEPENDENCIES
          );
        } else {
          var params = $injector.annotate(value);
          forEach(params, function (param) {
            if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);
          });
          plan.push(key, value, params);
        }
  
        cycle.pop();
        visited[key] = VISIT_DONE;
      }
      forEach(invocables, visit);
      invocables = cycle = visited = null; // plan is all that's required
  
      function isResolve(value) {
        return isObject(value) && value.then && value.$$promises;
      }
  
      return function (locals, parent, self) {
        if (isResolve(locals) && self === undefined) {
          self = parent;
          parent = locals;
          locals = null;
        }
        if (!locals) locals = NO_LOCALS;
        else if (!isObject(locals)) {
          throw new Error("'locals' must be an object");
        }
        if (!parent) parent = NO_PARENT;
        else if (!isResolve(parent)) {
          throw new Error("'parent' must be a promise returned by $resolve.resolve()");
        }
  
        // To complete the overall resolution, we have to wait for the parent
        // promise and for the promise for each invokable in our plan.
        var resolution = $q.defer(),
          result = resolution.promise,
          promises = (result.$$promises = {}),
          values = extend({}, locals),
          wait = 1 + plan.length / 3,
          merged = false;
  
        function done() {
          // Merge parent values we haven't got yet and publish our own $$values
          if (!--wait) {
            if (!merged) merge(values, parent.$$values);
            result.$$values = values;
            result.$$promises = result.$$promises || true; // keep for isResolve()
            delete result.$$inheritedValues;
            resolution.resolve(values);
          }
        }
  
        function fail(reason) {
          result.$$failure = reason;
          resolution.reject(reason);
        }
  
        // Short-circuit if parent has already failed
        if (isDefined(parent.$$failure)) {
          fail(parent.$$failure);
          return result;
        }
  
        if (parent.$$inheritedValues) {
          merge(values, omit(parent.$$inheritedValues, invocableKeys));
        }
  
        // Merge parent values if the parent has already resolved, or merge
        // parent promises and wait if the parent resolve is still in progress.
        extend(promises, parent.$$promises);
        if (parent.$$values) {
          merged = merge(values, omit(parent.$$values, invocableKeys));
          result.$$inheritedValues = omit(parent.$$values, invocableKeys);
          done();
        } else {
          if (parent.$$inheritedValues) {
            result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
          }
          parent.then(done, fail);
        }
  
        // Process each invocable in the plan, but ignore any where a local of the same name exists.
        for (var i = 0, ii = plan.length; i < ii; i += 3) {
          if (locals.hasOwnProperty(plan[i])) done();
          else invoke(plan[i], plan[i + 1], plan[i + 2]);
        }
  
        function invoke(key, invocable, params) {
          // Create a deferred for this invocation. Failures will propagate to the resolution as well.
          var invocation = $q.defer(),
            waitParams = 0;
          function onfailure(reason) {
            invocation.reject(reason);
            fail(reason);
          }
          // Wait for any parameter that we have a promise for (either from parent or from this
          // resolve; in that case study() will have made sure it's ordered before us in the plan).
          forEach(params, function (dep) {
            if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) {
              waitParams++;
              promises[dep].then(function (result) {
                values[dep] = result;
                if (!--waitParams) proceed();
              }, onfailure);
            }
          });
          if (!waitParams) proceed();
          function proceed() {
            if (isDefined(result.$$failure)) return;
            try {
              invocation.resolve($injector.invoke(invocable, self, values));
              invocation.promise.then(function (result) {
                values[key] = result;
                done();
              }, onfailure);
            } catch (e) {
              onfailure(e);
            }
          }
          // Publish promise synchronously; invocations further down in the plan may depend on it.
          promises[key] = invocation.promise;
        }
  
        return result;
      };
    };
  
    /**
     * @ngdoc function
     * @name ui.router.util.$resolve#resolve
     * @methodOf ui.router.util.$resolve
     *
     * @description
     * Resolves a set of invocables. An invocable is a function to be invoked via
     * `$injector.invoke()`, and can have an arbitrary number of dependencies.
     * An invocable can either return a value directly,
     * or a `$q` promise. If a promise is returned it will be resolved and the
     * resulting value will be used instead. Dependencies of invocables are resolved
     * (in this order of precedence)
     *
     * - from the specified `locals`
     * - from another invocable that is part of this `$resolve` call
     * - from an invocable that is inherited from a `parent` call to `$resolve`
     *   (or recursively
     * - from any ancestor `$resolve` of that parent).
     *
     * The return value of `$resolve` is a promise for an object that contains
     * (in this order of precedence)
     *
     * - any `locals` (if specified)
     * - the resolved return values of all injectables
     * - any values inherited from a `parent` call to `$resolve` (if specified)
     *
     * The promise will resolve after the `parent` promise (if any) and all promises
     * returned by injectables have been resolved. If any invocable
     * (or `$injector.invoke`) throws an exception, or if a promise returned by an
     * invocable is rejected, the `$resolve` promise is immediately rejected with the
     * same error. A rejection of a `parent` promise (if specified) will likewise be
     * propagated immediately. Once the `$resolve` promise has been rejected, no
     * further invocables will be called.
     *
     * Cyclic dependencies between invocables are not permitted and will caues `$resolve`
     * to throw an error. As a special case, an injectable can depend on a parameter
     * with the same name as the injectable, which will be fulfilled from the `parent`
     * injectable of the same name. This allows inherited values to be decorated.
     * Note that in this case any other injectable in the same `$resolve` with the same
     * dependency would see the decorated value, not the inherited value.
     *
     * Note that missing dependencies -- unlike cyclic dependencies -- will cause an
     * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous)
     * exception.
     *
     * Invocables are invoked eagerly as soon as all dependencies are available.
     * This is true even for dependencies inherited from a `parent` call to `$resolve`.
     *
     * As a special case, an invocable can be a string, in which case it is taken to
     * be a service name to be passed to `$injector.get()`. This is supported primarily
     * for backwards-compatibility with the `resolve` property of `$routeProvider`
     * routes.
     *
     * @param {object} invocables functions to invoke or
     * `$injector` services to fetch.
     * @param {object} locals  values to make available to the injectables
     * @param {object} parent  a promise returned by another call to `$resolve`.
     * @param {object} self  the `this` for the invoked methods
     * @return {object} Promise for an object that contains the resolved return value
     * of all invocables, as well as any inherited and local values.
     */
    this.resolve = function (invocables, locals, parent, self) {
      return this.study(invocables)(locals, parent, self);
    };
  }
  
  angular.module('ui.router.util').service('$resolve', $Resolve);
  