If you’ve dabbled with AngularJS, it’s very likely you’ve encountered $q. At ngEurope, there was a session devoted to $q, which gave a good overview of what it can do.
The ‘old way’ of doing asynchronous programming in javascript, is to use callbacks. But when your application becomes more complex, this leads to nested callbacks, which are ugly, unreadable, hard to maintain,… But they’re also not parallelizable, composable or dynamic.
With $q, you can stop the callback-madness. Instead of passing callbacks as parameters, you can start returning promises.
Using $q can seem a little confusing at first, but it is actually quite simple. Start by creating a deferred object with
$q.defer();
Then call
deffered.resolve()
when you’re done.
A user of your API (which could be yourself) can then call:
getSomething().then(function() {});
But $q can do more than just that. It can run functions in parallel:
$q.all([getA(), getB()]).then(function(responses) {});
The all-function returns a new promise and will only resolve when all functions are done. The responses-argument contains the responses of both functions. Or, if you prefer to have the different responses as separate arguments:
$q.all([getA(), getB()]).then(spread(function(a, b) {}));
Promises can be composed:
var promise1 = foo(); var promise2 = promise1.then(...); var promise3 = promise2.then(...);
The then-function returns a new promise every time.
Because promises don’t force you to chain at write-time, you can make all this dynamic. For example, based on if-else statements, different arrays of promises could be built and executed.
Promises can also notify when something went wrong. The ‘notifier’ doesn’t call deferred.resolve, but deferred.reject() instead. The receiver then has its second argument called:
getSomething().then(function() { alert('success'); }, function() { alert('error'); });
There is also a third arguments to indicate progress. This is a function that can be called multiple times, while resolve and reject can only be called once:
deferred.notify();
The when-function allows you to wrap functions/objects in a promise and return it. For example:
getMovies: function() { return $q.when(cachedMovies || fetchFromServer()); }
This allows for easy client-side caching. Even better would be to use this piece of code:
getMovies: function() { return $q.when(cachedMovies || p || fetchFromServer()); }
The ‘p’ variable should be set in the fetchFromServer-function and it should get the value of deferred.promise. This will avoid multiple http requests when we try to get the movies but haven’t yet returned from the previous call.
$q is not the same thing as Q, although it is based on it. $q is aware of the digest loop and is used throughout Angular.
I encourage you to start using promises as they are used everywhere, not just in Angular. Plus, native promises are coming soon.