The Power of Angular $q

Dave Smith

@djsmith42

http://thesmithfam.org/blog

No Bad Jokes

Typically I begin my presentations with a bad joke, but today, I promise I will not do that.

deferred.resolve(punchline);

Passing callbacks as function arguments

function getCurrentUser(callback) {
  $http.get("/api/users/me")
    .success(function(user) {
      callback(user);
    });
}

The Old Way

Calling It

getCurrentUser(function(user) {
    // Do something with user
});

Adding Another Request

function getPermissions(callback) {
  $http.get("/api/permissions")
    .success(function(permissions) {
        callback(permissions);
    });
}

Calling Both...

getCurrentUser(function(user) {
    // Do something with user
});

getPermissions(function(permissions) {
    // Do something with permissions
});

// Do something with user and permissions...
// How???

Serial?

getCurrentUser(function(user) {
    getPermissions(function(permissions) {
        // Do something with permissions
        // Do something with user
    });
});

Does not scale

getCurrentUser(function(user) {
    getPermissions(function(permissions) {
        getFoo(function(foo) {
            getBar(function(bar) {
                // :(                
            });
        });
    });
});

Problems:

  • Not parallelizable
  • Not composable
  • Not dynamic

With $q, we can do better!

But how do I use $q?

 

Two simple steps:

  • Stop passing callbacks as parameters
  • Start returning promises

The $q Way

function getCurrentUser() {
  var deferred = $q.defer();
  $http.get("/api/users/me")
     .success(function(user) {
        deferred.resolve(user);
     });
  return deferred.promise;
}

 

Calling It

getCurrentUser().then(function(user) {
    // Do something with user
});

Looks similar, but it's very different.

Why is this better?

Suddenly, all your async operations become:

  • Parallelizable
  • Composable
  • Dynamic

Parallelizable

 
$q.all([getCurrentUser(), getPermissions()])
    .then(function(responses) {
        var user = responses[0];
        var permissions = responses[1];
    });

By the way, this can be even nicer...

My favorite spread

My second favorite spread

function spread(func) {
    return function(array) {
        return func.apply(void 0, array);
    }
}

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void

​https://github.com/kriskowal/q/blob/v1/q.js

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

How to use spread:

 
$q.all([getCurrentUser(), getPermissions()])
  .then(spread(function(user, permissions) {
     // Do something with user and permissions
  }));

Almost as delicious as 

Composable

var promise1 = foo();
var promise2 = promise1.then(function() {...});
var promise3 = promise2.then(function() {...});



// By the way:
expect(promise1 === promise2).toBe(false);
expect(promise2 === promise3).toBe(false);

Promises are (mostly) immutable

Dynamic

var promises = [getCurrentUser()];
if (shouldGetPermissions) {
    promises.push(getPermissions());
}

$q.all(promises).then(function() {
    // all done!
});

"runtime logic" vs. "static logic"

What else?

Why this API separation?

deferred.resolve()

promise.then()

This forces a separation between notifier and receiver

What about errors?

deferred.reject()
promise.then(function() {
​  // success
},
function() {
  // failure

});

The notifier can send errors like this:

The receiver can receive them like this:

What about status?

deferred.notify(...)
promise.then(function() {
​  // success
},
function() {
  // failure
},
function() {
   // progress (called 0 or more times)
});

The notifier can send progress updates like this:

The receiver can receive them like this:

Deferreds: One Time Use

Once you resolve or reject a deferred object, it cannot be resolved or deferred again.

But you can be late to the party

You can connect a .then() to a promise after its deferred has been resolved, and your callback will be called at the next digest loop.

Last but not least

$q.when()
$q.when()

"Everything's better wrapped in a promise..."

"...even promises"

Easy Client-Side Caching

myApp.factory("movies", function($q, $http) {
  var cachedMovies;
  return {
    getMovies: function() {
      return $q.when(cachedMovies || helper());
    }
  };
  function helper() {
    var deferred = $q.defer();
    $http.get("/movies").success(function(movies) {
      cachedMovies = movies;
      deferred.resolve(movies);
    });
    return deferred.promise;
  }
});

Hardened Client-Side Caching

myApp.factory("movies", function($q, $http) {
  var cachedMovies, p;
  return {
    getMovies: function() {
      return $q.when(cachedMovies || p || helper());
    }
  };
  function helper() {
    var deferred = $q.defer();
    p = deferred.promise;
    $http.get("/movies").success(function(movies) {
      cachedMovies = movies;
      deferred.resolve(movies);
    });
    return deferred.promise;
  }
});

Side Note

$http already does caching.

Look at the "cache" argument.

 

 

 

https://docs.angularjs.org/api/ng/service/$http

Promises wrapped in  
  promises wrapped in
     promise wrapped in
        ...

Promises are Composable

$q is Angular's Champagne

  • All $http requests use $q
    • Request interceptors
    • Response interceptors
  • $interval and $timeout
  • ngAnimate
    • "ngAnimatias"
  • ngModelController (async validators)
  • $templateRequest

Why Did Angular Write $q?

  • Why not just the original "Q"?
  • $q is aware of the digest loop.
  • The Angular team is freaking brilliant.
 it('should simulate promise', inject(function($q, $rootScope) {
   var deferred = $q.defer();
   var promise = deferred.promise;
   var resolvedValue;

   promise.then(function(value) {
     resolvedValue = value;
   });

   deferred.resolve(123);
   expect(resolvedValue).toBeUndefined();

   $rootScope.$apply();
   expect(resolvedValue).toEqual(123);
 }));

Best Explained via Test

<-- What?

<-- Oh! Ok.

Promises are Everywhere

  • Even jQuery has them ;-)
  • Q
  • Bluebird
  • Native promises coming soon

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://promisesaplus.com/implementations

In Conclusion

function doSomething(callback) {
    ...
    callback();
}


function doSomething() {
    ...
    return promise;
}

The Power of Angular $q

By djsmith

The Power of Angular $q

  • 9,299