Extending $q promises in Angular

Why another post about this? Most implementations seem to overlook the fact that the changes applied to delegate.defer only affect the first promise in the chain, since by design the defer function used internally in Angular cannot be modified.

Since we can affect the first returned promise we have our way in. Now to make sure we stay “in”.

Lets break it down by going through the source.

deferred:

promise

$q methods:

$q.defer is our normal point of entry.

It all comes down to the functions that produce a new “internal” promise via defer.

By hooking into then, reject, when and all, and decorating every created promise before returning it, we should never have to encounter another non-decorated promise.

Example

For our purposes, lets add non-intercepting callbacks like the ones found on $http.

angular.module('myApp').config(function($provide) {
  $provide.decorator('$q', function($delegate) {
    // Extend promises with non-returning handlers
    function decoratePromise(promise) {
      promise._then = promise.then;
      promise.then = function(thenFn, errFn, notifyFn) {
        var p = promise._then(thenFn, errFn, notifyFn);
        return decoratePromise(p);
      };

      promise.success = function (fn) {
        promise.then(function (value) {
          fn(value);
        });
        return promise;
      };
      promise.error = function (fn) {
        promise.then(null, function (value) {
          fn(value);
        });
        return promise;
      };
      return promise;
    }

    var defer = $delegate.defer,
        when = $delegate.when,
        reject = $delegate.reject,
        all = $delegate.all;
    $delegate.defer = function() {
      var deferred = defer();
      decoratePromise(deferred.promise);
      return deferred;
    };
    $delegate.when = function() {
      var p = when.apply(this, arguments);
      return decoratePromise(p);
    };
    $delegate.reject = function() {
      var p = reject.apply(this, arguments);
      return decoratePromise(p);
    };
    $delegate.all = function() {
      var p = all.apply(this, arguments);
      return decoratePromise(p);
    };

    return $delegate;
  });
});

With this configuration block in place, all promises in your application will be able to listen in on the promise value without modifying it, using promise.success and promise.error, without breaking when you extend the chain with another then.

Posted in Programming with : Angular, Promises

comments powered by Disqus